From 36bada8feb8d1b0a47be367f4e1cd96088569d9d Mon Sep 17 00:00:00 2001 From: Nicolas Dorier Date: Thu, 12 Sep 2024 15:19:10 +0900 Subject: [PATCH] Uniformize Wallet API's path (#6209) * Uniformize Wallet API's path * Rewrite old API path to new API * Rename routes --- ...TCPayServerClient.OnChainPaymentMethods.cs | 6 +- ...TCPayServerClient.OnChainWallet.Objects.cs | 12 +- .../BTCPayServerClient.OnChainWallet.cs | 20 +- BTCPayServer.Tests/BTCPayServerTester.cs | 1 - BTCPayServer.Tests/GreenfieldAPITests.cs | 9 + BTCPayServer.Tests/ServerTester.cs | 2 - BTCPayServer.Tests/setup-dev-basics.sh | 4 +- ...ymentMethodsController.WalletGeneration.cs | 2 +- ...eldStoreOnChainPaymentMethodsController.cs | 6 +- ...GreenfieldStoreOnChainWalletsController.cs | 177 +++--- BTCPayServer/Hosting/Startup.cs | 4 + ...plate.stores-payment-methods.on-chain.json | 422 --------------- ...agger.template.stores-wallet.on-chain.json | 503 ++++++++++++++---- ...mplate.stores-wallet.on-chain.objects.json | 67 +-- 14 files changed, 561 insertions(+), 674 deletions(-) delete mode 100644 BTCPayServer/wwwroot/swagger/v1/swagger.template.stores-payment-methods.on-chain.json diff --git a/BTCPayServer.Client/BTCPayServerClient.OnChainPaymentMethods.cs b/BTCPayServer.Client/BTCPayServerClient.OnChainPaymentMethods.cs index 341509cc4..c7c224787 100644 --- a/BTCPayServer.Client/BTCPayServerClient.OnChainPaymentMethods.cs +++ b/BTCPayServer.Client/BTCPayServerClient.OnChainPaymentMethods.cs @@ -15,7 +15,7 @@ public partial class BTCPayServerClient int amount = 10, CancellationToken token = default) { - return await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/preview", + return await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/preview", new Dictionary { { "offset", offset }, { "amount", amount } }, new UpdatePaymentMethodRequest { Config = JValue.CreateString(derivationScheme) }, HttpMethod.Post, token); @@ -25,7 +25,7 @@ public partial class BTCPayServerClient string storeId, string paymentMethodId, int offset = 0, int amount = 10, CancellationToken token = default) { - return await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/preview", + return await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/preview", new Dictionary { { "offset", offset }, { "amount", amount } }, HttpMethod.Get, token); } @@ -33,7 +33,7 @@ public partial class BTCPayServerClient string paymentMethodId, GenerateOnChainWalletRequest request, CancellationToken token = default) { - return await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/generate", request, HttpMethod.Post, token); + return await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/generate", request, HttpMethod.Post, token); } } diff --git a/BTCPayServer.Client/BTCPayServerClient.OnChainWallet.Objects.cs b/BTCPayServer.Client/BTCPayServerClient.OnChainWallet.Objects.cs index 859157f1f..f2bb644cd 100644 --- a/BTCPayServer.Client/BTCPayServerClient.OnChainWallet.Objects.cs +++ b/BTCPayServer.Client/BTCPayServerClient.OnChainWallet.Objects.cs @@ -15,7 +15,7 @@ public partial class BTCPayServerClient parameters.Add("includeNeighbourData", v); try { - return await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects/{objectId.Type}/{objectId.Id}", parameters, HttpMethod.Get, token); + return await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/objects/{objectId.Type}/{objectId.Id}", parameters, HttpMethod.Get, token); } catch (GreenfieldAPIException err) when (err.APIError.Code == "wallet-object-not-found") { @@ -31,17 +31,17 @@ public partial class BTCPayServerClient parameters.Add("ids", ids); if (query?.IncludeNeighbourData is bool v) parameters.Add("includeNeighbourData", v); - return await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects", parameters, HttpMethod.Get, token); + return await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/objects", parameters, HttpMethod.Get, token); } public virtual async Task RemoveOnChainWalletObject(string storeId, string cryptoCode, OnChainWalletObjectId objectId, CancellationToken token = default) { - await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects/{objectId.Type}/{objectId.Id}", null, HttpMethod.Delete, token); + await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/objects/{objectId.Type}/{objectId.Id}", null, HttpMethod.Delete, token); } public virtual async Task AddOrUpdateOnChainWalletObject(string storeId, string cryptoCode, AddOnChainWalletObjectRequest request, CancellationToken token = default) { - return await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects", request, HttpMethod.Post, token); + return await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/objects", request, HttpMethod.Post, token); } public virtual async Task AddOrUpdateOnChainWalletLink(string storeId, string cryptoCode, @@ -49,7 +49,7 @@ public partial class BTCPayServerClient AddOnChainWalletObjectLinkRequest request = null, CancellationToken token = default) { - await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects/{objectId.Type}/{objectId.Id}/links", request, HttpMethod.Post, token); + await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/objects/{objectId.Type}/{objectId.Id}/links", request, HttpMethod.Post, token); } public virtual async Task RemoveOnChainWalletLinks(string storeId, string cryptoCode, @@ -57,6 +57,6 @@ public partial class BTCPayServerClient OnChainWalletObjectId link, CancellationToken token = default) { - await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects/{objectId.Type}/{objectId.Id}/links/{link.Type}/{link.Id}", null, HttpMethod.Delete, token); + await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/objects/{objectId.Type}/{objectId.Id}/links/{link.Type}/{link.Id}", null, HttpMethod.Delete, token); } } diff --git a/BTCPayServer.Client/BTCPayServerClient.OnChainWallet.cs b/BTCPayServer.Client/BTCPayServerClient.OnChainWallet.cs index f37e056f4..4a39dfb4c 100644 --- a/BTCPayServer.Client/BTCPayServerClient.OnChainWallet.cs +++ b/BTCPayServer.Client/BTCPayServerClient.OnChainWallet.cs @@ -14,7 +14,7 @@ public partial class BTCPayServerClient public virtual async Task ShowOnChainWalletOverview(string storeId, string cryptoCode, CancellationToken token = default) { - return await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet", null, HttpMethod.Get, token); + return await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet", null, HttpMethod.Get, token); } public virtual async Task GetOnChainFeeRate(string storeId, string cryptoCode, int? blockTarget = null, CancellationToken token = default) @@ -24,13 +24,13 @@ public partial class BTCPayServerClient { queryParams.Add("blockTarget", blockTarget); } - return await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/feeRate", queryParams, HttpMethod.Get, token); + return await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/feerate", queryParams, HttpMethod.Get, token); } public virtual async Task GetOnChainWalletReceiveAddress(string storeId, string cryptoCode, bool forceGenerate = false, CancellationToken token = default) { - return await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/address", new Dictionary + return await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/address", new Dictionary { {"forceGenerate", forceGenerate} }, HttpMethod.Get, token); @@ -39,7 +39,7 @@ public partial class BTCPayServerClient public virtual async Task UnReserveOnChainWalletReceiveAddress(string storeId, string cryptoCode, CancellationToken token = default) { - await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/address", null, HttpMethod.Delete, token); + await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/address", null, HttpMethod.Delete, token); } public virtual async Task> ShowOnChainWalletTransactions( @@ -59,14 +59,14 @@ public partial class BTCPayServerClient { query.Add(nameof(skip), skip); } - return await SendHttpRequest>($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions", query, HttpMethod.Get, token); + return await SendHttpRequest>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/transactions", query, HttpMethod.Get, token); } public virtual async Task GetOnChainWalletTransaction( string storeId, string cryptoCode, string transactionId, CancellationToken token = default) { - return await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions/{transactionId}", null, HttpMethod.Get, token); + return await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/transactions/{transactionId}", null, HttpMethod.Get, token); } public virtual async Task PatchOnChainWalletTransaction( @@ -74,7 +74,7 @@ public partial class BTCPayServerClient PatchOnChainTransactionRequest request, bool force = false, CancellationToken token = default) { - return await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions/{transactionId}", + return await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/transactions/{transactionId}", new Dictionary { {"force", force} }, request, HttpMethod.Patch, token); } @@ -82,7 +82,7 @@ public partial class BTCPayServerClient string cryptoCode, CancellationToken token = default) { - return await SendHttpRequest>($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/utxos", null, HttpMethod.Get, token); + return await SendHttpRequest>($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/utxos", null, HttpMethod.Get, token); } public virtual async Task CreateOnChainTransaction(string storeId, @@ -94,7 +94,7 @@ public partial class BTCPayServerClient throw new ArgumentOutOfRangeException(nameof(request.ProceedWithBroadcast), "Please use CreateOnChainTransactionButDoNotBroadcast when wanting to only create the transaction"); } - return await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions", request, HttpMethod.Post, token); + return await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/transactions", request, HttpMethod.Post, token); } public virtual async Task CreateOnChainTransactionButDoNotBroadcast(string storeId, @@ -106,6 +106,6 @@ public partial class BTCPayServerClient throw new ArgumentOutOfRangeException(nameof(request.ProceedWithBroadcast), "Please use CreateOnChainTransaction when wanting to also broadcast the transaction"); } - return Transaction.Parse(await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions", request, HttpMethod.Post, token), network); + return Transaction.Parse(await SendHttpRequest($"api/v1/stores/{storeId}/payment-methods/{cryptoCode}-CHAIN/wallet/transactions", request, HttpMethod.Post, token), network); } } diff --git a/BTCPayServer.Tests/BTCPayServerTester.cs b/BTCPayServer.Tests/BTCPayServerTester.cs index d8b54386e..24aafef04 100644 --- a/BTCPayServer.Tests/BTCPayServerTester.cs +++ b/BTCPayServer.Tests/BTCPayServerTester.cs @@ -177,7 +177,6 @@ namespace BTCPayServer.Tests l.AddFilter("System.Net.Http.HttpClient", LogLevel.Critical); l.SetMinimumLevel(LogLevel.Information) .AddFilter("Microsoft", LogLevel.Error) - .AddFilter("Hangfire", LogLevel.Error) .AddFilter("Microsoft.EntityFrameworkCore.Migrations", LogLevel.Information) .AddFilter("Fido2NetLib.DistributedCacheMetadataService", LogLevel.Error) .AddProvider(LoggerProvider); diff --git a/BTCPayServer.Tests/GreenfieldAPITests.cs b/BTCPayServer.Tests/GreenfieldAPITests.cs index ca3ebedaf..3bfbc0c92 100644 --- a/BTCPayServer.Tests/GreenfieldAPITests.cs +++ b/BTCPayServer.Tests/GreenfieldAPITests.cs @@ -2,6 +2,7 @@ using System; using System.Collections.Generic; using System.Collections.Immutable; using System.Linq; +using System.Net.Http; using System.Security.Cryptography; using System.Threading; using System.Threading.Tasks; @@ -3105,6 +3106,8 @@ namespace BTCPayServer.Tests }); Assert.Equal(firstAddress, (await viewOnlyClient.PreviewProposedStoreOnChainPaymentMethodAddresses(store.Id, "BTC", xpub)).Addresses.First().Address); + // Testing if the rewrite rule to old API path is working + await viewOnlyClient.SendHttpRequest($"api/v1/stores/{store.Id}/payment-methods/onchain/BTC/preview", new JObject() { ["config"] = xpub.ToString() }, HttpMethod.Post); var method = await client.UpdateStorePaymentMethod(store.Id, "BTC-CHAIN", new UpdatePaymentMethodRequest() { Enabled = true, Config = JValue.CreateString(xpub.ToString())}); @@ -3384,6 +3387,12 @@ namespace BTCPayServer.Tests { await viewOnlyClient.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode); }); + + // Testing if the rewrite rule to old API path is working + await AssertHttpError(403, async () => + { + await viewOnlyClient.SendHttpRequest($"api/v1/stores/{walletId.StoreId}/payment-methods/onchain/{walletId.CryptoCode}/wallet/address", null as object); + }); var address = await client.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode); var address2 = await client.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode); var address3 = await client.GetOnChainWalletReceiveAddress(walletId.StoreId, walletId.CryptoCode, true); diff --git a/BTCPayServer.Tests/ServerTester.cs b/BTCPayServer.Tests/ServerTester.cs index 46aaf21f9..fe0f991be 100644 --- a/BTCPayServer.Tests/ServerTester.cs +++ b/BTCPayServer.Tests/ServerTester.cs @@ -247,8 +247,6 @@ namespace BTCPayServer.Tests get; set; } - readonly HttpClient _Http = new HttpClient(); - public BTCPayServerTester PayTester { get; set; diff --git a/BTCPayServer.Tests/setup-dev-basics.sh b/BTCPayServer.Tests/setup-dev-basics.sh index de027b2b5..b6ada9ebe 100755 --- a/BTCPayServer.Tests/setup-dev-basics.sh +++ b/BTCPayServer.Tests/setup-dev-basics.sh @@ -83,7 +83,7 @@ curl -s -k -X PUT -H 'Content-Type: application/json' \ # Fund Satoshis Steaks wallet btcaddress_satoshis_steaks=$(curl -s -k -X GET -H 'Content-Type: application/json' \ -H "Authorization: token $admin_api_key" \ - "$API_BASE/stores/$store_id_satoshis_steaks/payment-methods/onchain/BTC/wallet/address" | jq -r '.address') + "$API_BASE/stores/$store_id_satoshis_steaks/payment-methods/BTC-CHAIN/wallet/address" | jq -r '.address') ./docker-bitcoin-cli.sh sendtoaddress "$btcaddress_satoshis_steaks" 6.15 >/dev/null 2>&1 @@ -167,7 +167,7 @@ printf "Nakamoto Nuggets Cart POS ID: %s\n" "$cart_app_id_nakamoto_nuggets" # Fund Nakamoto Nuggets wallet btcaddress_nakamoto_nuggets=$(curl -s -k -X GET -H 'Content-Type: application/json' \ -H "Authorization: token $admin_api_key" \ - "$API_BASE/stores/$store_id_nakamoto_nuggets/payment-methods/onchain/BTC/wallet/address" | jq -r '.address') + "$API_BASE/stores/$store_id_nakamoto_nuggets/payment-methods/BTC-CHAIN/wallet/address" | jq -r '.address') ./docker-bitcoin-cli.sh sendtoaddress "$btcaddress_nakamoto_nuggets" 6.15 >/dev/null 2>&1 diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldStoreOnChainPaymentMethodsController.WalletGeneration.cs b/BTCPayServer/Controllers/GreenField/GreenfieldStoreOnChainPaymentMethodsController.WalletGeneration.cs index ecbb89a41..2b12b8be1 100644 --- a/BTCPayServer/Controllers/GreenField/GreenfieldStoreOnChainPaymentMethodsController.WalletGeneration.cs +++ b/BTCPayServer/Controllers/GreenField/GreenfieldStoreOnChainPaymentMethodsController.WalletGeneration.cs @@ -19,7 +19,7 @@ namespace BTCPayServer.Controllers.Greenfield public partial class GreenfieldStoreOnChainPaymentMethodsController { [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] - [HttpPost("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/generate")] + [HttpPost("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/generate")] [EnableCors(CorsPolicies.All)] public async Task GenerateOnChainWallet(string storeId, [ModelBinder(typeof(PaymentMethodIdModelBinder))] diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldStoreOnChainPaymentMethodsController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldStoreOnChainPaymentMethodsController.cs index 6a853c966..3cc52d5f0 100644 --- a/BTCPayServer/Controllers/GreenField/GreenfieldStoreOnChainPaymentMethodsController.cs +++ b/BTCPayServer/Controllers/GreenField/GreenfieldStoreOnChainPaymentMethodsController.cs @@ -66,11 +66,11 @@ namespace BTCPayServer.Controllers.Greenfield protected JsonHttpException ErrorPaymentMethodNotConfigured() { - return new JsonHttpException(this.CreateAPIError(404, "paymentmethod-not-configured", "The lightning node is not set up")); + return new JsonHttpException(this.CreateAPIError(404, "paymentmethod-not-configured", "The payment method is not configured")); } [Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] - [HttpGet("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/preview")] + [HttpGet("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/preview")] public IActionResult GetOnChainPaymentMethodPreview( string storeId, [ModelBinder(typeof(PaymentMethodIdModelBinder))] @@ -87,7 +87,7 @@ namespace BTCPayServer.Controllers.Greenfield [Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] - [HttpPost("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/preview")] + [HttpPost("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/preview")] public async Task GetProposedOnChainPaymentMethodPreview( string storeId, [ModelBinder(typeof(PaymentMethodIdModelBinder))] diff --git a/BTCPayServer/Controllers/GreenField/GreenfieldStoreOnChainWalletsController.cs b/BTCPayServer/Controllers/GreenField/GreenfieldStoreOnChainWalletsController.cs index dd5de05e2..5646b7d24 100644 --- a/BTCPayServer/Controllers/GreenField/GreenfieldStoreOnChainWalletsController.cs +++ b/BTCPayServer/Controllers/GreenField/GreenfieldStoreOnChainWalletsController.cs @@ -95,10 +95,10 @@ namespace BTCPayServer.Controllers.Greenfield } [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] - [HttpGet("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet")] - public async Task ShowOnChainWalletOverview(string storeId, string cryptoCode) + [HttpGet("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet")] + public async Task ShowOnChainWalletOverview(string storeId, string paymentMethodId) { - if (IsInvalidWalletRequest(cryptoCode, out var network, + if (IsInvalidWalletRequest(paymentMethodId, out var network, out var derivationScheme, out var actionResult)) return actionResult; @@ -115,10 +115,10 @@ namespace BTCPayServer.Controllers.Greenfield } [Authorize(Policy = Policies.CanViewStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] - [HttpGet("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/feerate")] - public async Task GetOnChainFeeRate(string storeId, string cryptoCode, int? blockTarget = null) + [HttpGet("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/feerate")] + public async Task GetOnChainFeeRate(string storeId, string paymentMethodId, int? blockTarget = null) { - if (IsInvalidWalletRequest(cryptoCode, out var network, + if (IsInvalidWalletRequest(paymentMethodId, out var network, out _, out var actionResult)) return actionResult; @@ -131,15 +131,15 @@ namespace BTCPayServer.Controllers.Greenfield } [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] - [HttpGet("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/address")] - public async Task GetOnChainWalletReceiveAddress(string storeId, string cryptoCode, + [HttpGet("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/address")] + public async Task GetOnChainWalletReceiveAddress(string storeId, string paymentMethodId, bool forceGenerate = false) { - if (IsInvalidWalletRequest(cryptoCode, out var network, + if (IsInvalidWalletRequest(paymentMethodId, out var network, out var derivationScheme, out var actionResult)) return actionResult; - var kpi = await _walletReceiveService.GetOrGenerate(new WalletId(storeId, cryptoCode), forceGenerate); + var kpi = await _walletReceiveService.GetOrGenerate(new WalletId(storeId, network.CryptoCode), forceGenerate); if (kpi is null) { return BadRequest(); @@ -151,7 +151,7 @@ namespace BTCPayServer.Controllers.Greenfield { bip21.QueryParams.Add(PayjoinClient.BIP21EndpointKey, Request.GetAbsoluteUri(Url.Action(nameof(PayJoinEndpointController.Submit), "PayJoinEndpoint", - new { cryptoCode }))); + new { network.CryptoCode }))); } return Ok(new OnChainWalletAddressData() @@ -163,28 +163,28 @@ namespace BTCPayServer.Controllers.Greenfield } [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] - [HttpDelete("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/address")] - public async Task UnReserveOnChainWalletReceiveAddress(string storeId, string cryptoCode) + [HttpDelete("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/address")] + public async Task UnReserveOnChainWalletReceiveAddress(string storeId, string paymentMethodId) { - if (IsInvalidWalletRequest(cryptoCode, out _, + if (IsInvalidWalletRequest(paymentMethodId, out var network, out _, out var actionResult)) return actionResult; - var addr = await _walletReceiveService.UnReserveAddress(new WalletId(storeId, cryptoCode)); + var addr = await _walletReceiveService.UnReserveAddress(new WalletId(storeId, network.CryptoCode)); if (addr is null) { return this.CreateAPIError("no-reserved-address", - $"There was no reserved address for {cryptoCode} on this store."); + $"There was no reserved address for {network.CryptoCode} on this store."); } return Ok(); } [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] - [HttpGet("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions")] + [HttpGet("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/transactions")] public async Task ShowOnChainWalletTransactions( string storeId, - string cryptoCode, + string paymentMethodId, [FromQuery] TransactionStatus[]? statusFilter = null, [FromQuery] string? labelFilter = null, [FromQuery] int skip = 0, @@ -192,12 +192,12 @@ namespace BTCPayServer.Controllers.Greenfield CancellationToken cancellationToken = default ) { - if (IsInvalidWalletRequest(cryptoCode, out var network, + if (IsInvalidWalletRequest(paymentMethodId, out var network, out var derivationScheme, out var actionResult)) return actionResult; var wallet = _btcPayWalletProvider.GetWallet(network); - var walletId = new WalletId(storeId, cryptoCode); + var walletId = new WalletId(storeId, network.CryptoCode); var walletTransactionsInfoAsync = await _walletRepository.GetWalletTransactionsInfo(walletId, (string[]?)null); var preFiltering = true; @@ -238,11 +238,11 @@ namespace BTCPayServer.Controllers.Greenfield } [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] - [HttpGet("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions/{transactionId}")] - public async Task GetOnChainWalletTransaction(string storeId, string cryptoCode, + [HttpGet("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/transactions/{transactionId}")] + public async Task GetOnChainWalletTransaction(string storeId, string paymentMethodId, string transactionId) { - if (IsInvalidWalletRequest(cryptoCode, out var network, + if (IsInvalidWalletRequest(paymentMethodId, out var network, out var derivationScheme, out var actionResult)) return actionResult; @@ -253,7 +253,7 @@ namespace BTCPayServer.Controllers.Greenfield return this.CreateAPIError(404, "transaction-not-found", "The transaction was not found."); } - var walletId = new WalletId(storeId, cryptoCode); + var walletId = new WalletId(storeId, network.CryptoCode); var walletTransactionsInfoAsync = (await _walletRepository.GetWalletTransactionsInfo(walletId, new[] { transactionId })).Values .FirstOrDefault(); @@ -263,16 +263,16 @@ namespace BTCPayServer.Controllers.Greenfield [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] [HttpPatch( - "~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions/{transactionId}")] + "~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/transactions/{transactionId}")] public async Task PatchOnChainWalletTransaction( string storeId, - string cryptoCode, + string paymentMethodId, string transactionId, [FromBody] PatchOnChainTransactionRequest request, bool force = false ) { - if (IsInvalidWalletRequest(cryptoCode, out var network, + if (IsInvalidWalletRequest(paymentMethodId, out var network, out var derivationScheme, out var actionResult)) return actionResult; @@ -283,7 +283,7 @@ namespace BTCPayServer.Controllers.Greenfield return this.CreateAPIError(404, "transaction-not-found", "The transaction was not found."); } - var walletId = new WalletId(storeId, cryptoCode); + var walletId = new WalletId(storeId, network.CryptoCode); var txObjectId = new WalletObjectId(walletId, WalletObjectData.Types.Tx, transactionId); if (request.Comment != null) @@ -305,20 +305,20 @@ namespace BTCPayServer.Controllers.Greenfield } [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] - [HttpGet("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/utxos")] - public async Task GetOnChainWalletUTXOs(string storeId, string cryptoCode) + [HttpGet("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/utxos")] + public async Task GetOnChainWalletUTXOs(string storeId, string paymentMethodId) { - if (IsInvalidWalletRequest(cryptoCode, out var network, + if (IsInvalidWalletRequest(paymentMethodId, out var network, out var derivationScheme, out var actionResult)) return actionResult; var wallet = _btcPayWalletProvider.GetWallet(network); - var walletId = new WalletId(storeId, cryptoCode); + var walletId = new WalletId(storeId, network.CryptoCode); var utxos = await wallet.GetUnspentCoins(derivationScheme.AccountDerivation); var walletTransactionsInfoAsync = await _walletRepository.GetWalletTransactionsInfo(walletId, utxos.SelectMany(GetWalletObjectsQuery.Get).Distinct().ToArray()); - var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode); + var pmi = PaymentMethodId.Parse(paymentMethodId); return Ok(utxos.Select(coin => { walletTransactionsInfoAsync.TryGetValue(coin.OutPoint.Hash.ToString(), out var info1); @@ -347,17 +347,17 @@ namespace BTCPayServer.Controllers.Greenfield } [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] - [HttpPost("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions")] - public async Task CreateOnChainTransaction(string storeId, string cryptoCode, + [HttpPost("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/transactions")] + public async Task CreateOnChainTransaction(string storeId, string paymentMethodId, [FromBody] CreateOnChainTransactionRequest request) { - if (IsInvalidWalletRequest(cryptoCode, out var network, + if (IsInvalidWalletRequest(paymentMethodId, out var network, out var derivationScheme, out var actionResult)) return actionResult; if (network.ReadonlyWallet) { return this.CreateAPIError(503, "not-available", - $"{cryptoCode} sending services are not currently available"); + $"{network.CryptoCode} sending services are not currently available"); } //This API is only meant for hot wallet usage for now. We can expand later when we allow PSBT manipulation. @@ -387,7 +387,7 @@ namespace BTCPayServer.Controllers.Greenfield return this.CreateValidationError(ModelState); } - var explorerClient = _explorerClientProvider.GetExplorerClient(cryptoCode); + var explorerClient = _explorerClientProvider.GetExplorerClient(network); var wallet = _btcPayWalletProvider.GetWallet(network); var utxos = await wallet.GetUnspentCoins(derivationScheme.AccountDerivation, request.ExcludeUnconfirmed); @@ -552,7 +552,7 @@ namespace BTCPayServer.Controllers.Greenfield if (!derivationScheme.IsHotWallet || signingKeyStr is null) { return this.CreateAPIError(503, "not-available", - $"{cryptoCode} sending services are not currently available"); + $"{network.CryptoCode} sending services are not currently available"); } var signingKey = ExtKey.Parse(signingKeyStr, network.NBitcoinNetwork); @@ -599,12 +599,12 @@ namespace BTCPayServer.Controllers.Greenfield payjoinPSBT.Finalize(); var payjoinTransaction = payjoinPSBT.ExtractTransaction(); var hash = payjoinTransaction.GetHash(); - await this._walletRepository.AddWalletTransactionAttachment(new WalletId(Store.Id, cryptoCode), + await this._walletRepository.AddWalletTransactionAttachment(new WalletId(Store.Id, network.CryptoCode), hash, Attachment.Payjoin()); broadcastResult = await explorerClient.BroadcastAsync(payjoinTransaction); if (broadcastResult.Success) { - return await GetOnChainWalletTransaction(storeId, cryptoCode, hash.ToString()); + return await GetOnChainWalletTransaction(storeId, paymentMethodId, hash.ToString()); } } catch (PayjoinException) @@ -621,7 +621,7 @@ namespace BTCPayServer.Controllers.Greenfield broadcastResult = await explorerClient.BroadcastAsync(transaction); if (broadcastResult.Success) { - return await GetOnChainWalletTransaction(storeId, cryptoCode, transactionHash.ToString()); + return await GetOnChainWalletTransaction(storeId, paymentMethodId, transactionHash.ToString()); } else { @@ -629,9 +629,9 @@ namespace BTCPayServer.Controllers.Greenfield } } - [HttpGet("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects")] + [HttpGet("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/objects")] [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] - public async Task GetOnChainWalletObjects(string storeId, string cryptoCode, string? type = null, [FromQuery(Name = "ids")] string[]? ids = null, bool? includeNeighbourData = null) + public async Task GetOnChainWalletObjects(string storeId, string paymentMethodId, string? type = null, [FromQuery(Name = "ids")] string[]? ids = null, bool? includeNeighbourData = null) { if (ids?.Length is 0 && !Request.Query.ContainsKey("ids")) ids = null; @@ -639,28 +639,34 @@ namespace BTCPayServer.Controllers.Greenfield ModelState.AddModelError(nameof(ids), "If ids is specified, type should be specified"); if (!ModelState.IsValid) return this.CreateValidationError(ModelState); - var walletId = new WalletId(storeId, cryptoCode); + if (IsInvalidWalletRequest(paymentMethodId, out var network, out var actionResult)) + return actionResult; + var walletId = new WalletId(storeId, network.CryptoCode); return Ok((await _walletRepository.GetWalletObjects(new(walletId, type, ids) { IncludeNeighbours = includeNeighbourData ?? true })).Select(kv => kv.Value).Select(ToModel).ToArray()); } - [HttpGet("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects/{objectType}/{objectId}")] + [HttpGet("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/objects/{objectType}/{objectId}")] [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] - public async Task GetOnChainWalletObject(string storeId, string cryptoCode, + public async Task GetOnChainWalletObject(string storeId, string paymentMethodId, string objectType, string objectId, bool? includeNeighbourData = null) { - var walletId = new WalletId(storeId, cryptoCode); + if (IsInvalidWalletRequest(paymentMethodId, out var network, out var actionResult)) + return actionResult; + var walletId = new WalletId(storeId, network.CryptoCode); var wo = await _walletRepository.GetWalletObject(new(walletId, objectType, objectId), includeNeighbourData ?? true); if (wo is null) return WalletObjectNotFound(); return Ok(ToModel(wo)); } - [HttpDelete("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects/{objectType}/{objectId}")] + [HttpDelete("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/objects/{objectType}/{objectId}")] [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] - public async Task RemoveOnChainWalletObject(string storeId, string cryptoCode, + public async Task RemoveOnChainWalletObject(string storeId, string paymentMethodId, string objectType, string objectId) { - var walletId = new WalletId(storeId, cryptoCode); + if (IsInvalidWalletRequest(paymentMethodId, out var network, out var actionResult)) + return actionResult; + var walletId = new WalletId(storeId, network.CryptoCode); if (await _walletRepository.RemoveWalletObjects(new WalletObjectId(walletId, objectType, objectId))) return Ok(); else @@ -672,11 +678,14 @@ namespace BTCPayServer.Controllers.Greenfield return this.CreateAPIError(404, "wallet-object-not-found", "This wallet object's can't be found"); } - [HttpPost("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects")] + [HttpPost("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/objects")] [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] - public async Task AddOrUpdateOnChainWalletObject(string storeId, string cryptoCode, + public async Task AddOrUpdateOnChainWalletObject(string storeId, + string paymentMethodId, [FromBody] AddOnChainWalletObjectRequest request) { + if (IsInvalidWalletRequest(paymentMethodId, out var network, out var actionResult)) + return actionResult; if (request?.Type is null) ModelState.AddModelError(nameof(request.Type), "Type is required"); if (request?.Id is null) @@ -684,13 +693,13 @@ namespace BTCPayServer.Controllers.Greenfield if (!ModelState.IsValid) return this.CreateValidationError(ModelState); - var walletId = new WalletId(storeId, cryptoCode); + var walletId = new WalletId(storeId, network.CryptoCode); try { await _walletRepository.SetWalletObject( new WalletObjectId(walletId, request!.Type, request.Id), request.Data); - return await GetOnChainWalletObject(storeId, cryptoCode, request!.Type, request.Id); + return await GetOnChainWalletObject(storeId, network.CryptoCode, request!.Type, request.Id); } catch (DbUpdateException) { @@ -698,12 +707,14 @@ namespace BTCPayServer.Controllers.Greenfield } } - [HttpPost("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects/{objectType}/{objectId}/links")] + [HttpPost("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/objects/{objectType}/{objectId}/links")] [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] - public async Task AddOrUpdateOnChainWalletLinks(string storeId, string cryptoCode, + public async Task AddOrUpdateOnChainWalletLinks(string storeId, string paymentMethodId, string objectType, string objectId, [FromBody] AddOnChainWalletObjectLinkRequest request) { + if (IsInvalidWalletRequest(paymentMethodId, out var network, out var actionResult)) + return actionResult; if (request?.Type is null) ModelState.AddModelError(nameof(request.Type), "Type is required"); if (request?.Id is null) @@ -711,7 +722,7 @@ namespace BTCPayServer.Controllers.Greenfield if (!ModelState.IsValid) return this.CreateValidationError(ModelState); - var walletId = new WalletId(storeId, cryptoCode); + var walletId = new WalletId(storeId, network.CryptoCode); try { await _walletRepository.SetWalletObjectLink( @@ -726,13 +737,15 @@ namespace BTCPayServer.Controllers.Greenfield } } - [HttpDelete("~/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects/{objectType}/{objectId}/links/{linkType}/{linkId}")] + [HttpDelete("~/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/objects/{objectType}/{objectId}/links/{linkType}/{linkId}")] [Authorize(Policy = Policies.CanModifyStoreSettings, AuthenticationSchemes = AuthenticationSchemes.Greenfield)] - public async Task RemoveOnChainWalletLink(string storeId, string cryptoCode, + public async Task RemoveOnChainWalletLink(string storeId, string paymentMethodId, string objectType, string objectId, string linkType, string linkId) { - var walletId = new WalletId(storeId, cryptoCode); + if (IsInvalidWalletRequest(paymentMethodId, out var network, out var actionResult)) + return actionResult; + var walletId = new WalletId(storeId, network.CryptoCode); if (await _walletRepository.RemoveWalletObjectLink( new WalletObjectId(walletId, objectType, objectId), new WalletObjectId(walletId, linkType, linkId))) @@ -769,31 +782,19 @@ namespace BTCPayServer.Controllers.Greenfield return await _authorizationService.CanUseHotWallet(PoliciesSettings, User); } - private bool IsInvalidWalletRequest(string cryptoCode, [MaybeNullWhen(true)] out BTCPayNetwork network, + private bool IsInvalidWalletRequest(string paymentMethodId, [MaybeNullWhen(true)] out BTCPayNetwork network, [MaybeNullWhen(true)] out DerivationSchemeSettings derivationScheme, [MaybeNullWhen(false)] out IActionResult actionResult) { - var pmi = PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode); derivationScheme = null; - if (!_handlers.TryGetValue(pmi, out var handler)) - { - throw new JsonHttpException(this.CreateAPIError(404, "unknown-cryptocode", - "This crypto code isn't set up in this BTCPay Server instance")); - } - network = ((IHasNetwork)handler).Network; - - if (!network.WalletSupported || !_btcPayWalletProvider.IsAvailable(network)) - { - actionResult = this.CreateAPIError(503, "not-available", - $"{cryptoCode} services are not currently available"); + if (IsInvalidWalletRequest(paymentMethodId, out network, out actionResult)) return true; - } - derivationScheme = GetDerivationSchemeSettings(cryptoCode); + derivationScheme = GetDerivationSchemeSettings(network.CryptoCode); if (derivationScheme?.AccountDerivation is null) { actionResult = this.CreateAPIError(503, "not-available", - $"{cryptoCode} doesn't have any derivation scheme set"); + $"{network.CryptoCode} doesn't have any derivation scheme set"); return true; } @@ -801,6 +802,28 @@ namespace BTCPayServer.Controllers.Greenfield return false; } + private bool IsInvalidWalletRequest(string paymentMethodId, [MaybeNullWhen(true)] out BTCPayNetwork network, + [MaybeNullWhen(false)] out IActionResult actionResult) + { + if (!PaymentMethodId.TryParse(paymentMethodId, out var pmi) + || !_handlers.TryGetValue(pmi, out var handler) + || handler is not IHasNetwork { Network: { WalletSupported: true } }) + { + throw new JsonHttpException(this.CreateAPIError(404, "unknown-paymentMethodId", + "This payment method doesn't exists or doesn't offer wallet services")); + } + network = ((IHasNetwork)handler).Network; + + if (!_btcPayWalletProvider.IsAvailable(network)) + { + actionResult = this.CreateAPIError(503, "not-available", + $"{pmi} services are not currently available"); + return true; + } + actionResult = null; + return false; + } + private DerivationSchemeSettings? GetDerivationSchemeSettings(string cryptoCode) { return Store.GetPaymentMethodConfig(PaymentTypes.CHAIN.GetPaymentMethodId(cryptoCode), _handlers); diff --git a/BTCPayServer/Hosting/Startup.cs b/BTCPayServer/Hosting/Startup.cs index 1ba554e64..de294a8ec 100644 --- a/BTCPayServer/Hosting/Startup.cs +++ b/BTCPayServer/Hosting/Startup.cs @@ -281,6 +281,10 @@ namespace BTCPayServer.Hosting var rewriteOptions = new RewriteOptions(); rewriteOptions.AddRewrite("_blazor/(negotiate|initializers|disconnect)$", "/_blazor/$1", skipRemainingRules: true); rewriteOptions.AddRewrite("_blazor$", "/_blazor", skipRemainingRules: true); + + // A rewrite rule to support the old API + rewriteOptions.AddRewrite("api/v1/stores/([^/]+)/payment-methods/[Oo]n[Cc]hain/([^/]+)/(preview|generate)", "/api/v1/stores/$1/payment-methods/$2-CHAIN/wallet/$3", skipRemainingRules: true); + rewriteOptions.AddRewrite("api/v1/stores/([^/]+)/payment-methods/[Oo]n[Cc]hain/([^/]+)(.*)", "/api/v1/stores/$1/payment-methods/$2-CHAIN$3", skipRemainingRules: true); app.UseRewriter(rewriteOptions); app.UseHeadersOverride(); diff --git a/BTCPayServer/wwwroot/swagger/v1/swagger.template.stores-payment-methods.on-chain.json b/BTCPayServer/wwwroot/swagger/v1/swagger.template.stores-payment-methods.on-chain.json deleted file mode 100644 index c881693f7..000000000 --- a/BTCPayServer/wwwroot/swagger/v1/swagger.template.stores-payment-methods.on-chain.json +++ /dev/null @@ -1,422 +0,0 @@ -{ - "paths": { - "/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/generate": { - "post": { - "tags": [ - "Store Payment Methods (On Chain)" - ], - "summary": "Generate store on-chain wallet", - "parameters": [ - { - "name": "storeId", - "in": "path", - "required": true, - "description": "The store to fetch", - "schema": { - "type": "string" - } - }, - { - "$ref": "#/components/parameters/PaymentMethodId" - } - ], - "description": "Generate a wallet and update the specified store's payment method to it", - "requestBody": { - "x-name": "request", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/GenerateOnChainWalletRequest" - } - } - }, - "required": true, - "x-position": 1 - }, - "operationId": "StoreOnChainPaymentMethods_GenerateOnChainWallet", - "responses": { - "200": { - "description": "updated specified payment method with the generated wallet", - "content": { - "application/json": { - "schema": { - "allOf": [ - { - "$ref": "#/components/schemas/GenericPaymentMethodData" - }, - { - "type": "object", - "properties": { - "mnemonic": { - "$ref": "#/components/schemas/Mnemonic" - }, - "config": { - "$ref": "#/components/schemas/OnChainPaymentMethodBaseData" - } - } - } - ] - } - } - } - }, - "400": { - "description": "A list of errors that occurred when updating the store payment method", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/ValidationProblemDetails" - } - } - } - }, - "403": { - "description": "If you are authenticated but forbidden to update the specified store" - }, - "404": { - "description": "The key is not found for this store" - } - }, - "security": [ - { - "API_Key": [ - "btcpay.store.canmodifystoresettings" - ], - "Basic": [] - } - ] - } - }, - "/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/preview": { - "get": { - "tags": [ - "Store Payment Methods (On Chain)" - ], - "summary": "Preview store on-chain payment method addresses", - "parameters": [ - { - "name": "storeId", - "in": "path", - "required": true, - "description": "The store to fetch", - "schema": { - "type": "string" - } - }, - { - "name": "paymentMethodId", - "in": "path", - "required": true, - "description": "The payment method id of the payment method to update", - "schema": { - "type": "string" - }, - "example": "BTC-CHAIN" - }, - { - "name": "offset", - "in": "query", - "required": false, - "description": "From which index to fetch the addresses", - "schema": { - "type": "number" - } - }, - { - "name": "count", - "in": "query", - "required": false, - "description": "Number of addresses to preview", - "schema": { - "type": "number" - } - } - ], - "description": "View addresses of the current payment method of the store", - "operationId": "StoreOnChainPaymentMethods_GetOnChainPaymentMethodPreview", - "responses": { - "200": { - "description": "specified payment method addresses", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/OnChainPaymentMethodPreviewResultData" - } - } - } - }, - "403": { - "description": "If you are authenticated but forbidden to view the specified store" - }, - "404": { - "description": "The key is not found for this store/payment method" - } - }, - "security": [ - { - "API_Key": [ - "btcpay.store.canviewstoresettings" - ], - "Basic": [] - } - ] - }, - "post": { - "tags": [ - "Store Payment Methods (On Chain)" - ], - "summary": "Preview proposed store on-chain payment method addresses", - "parameters": [ - { - "name": "storeId", - "in": "path", - "required": true, - "description": "The store to fetch", - "schema": { - "type": "string" - } - }, - { - "$ref": "#/components/parameters/PaymentMethodId" - }, - { - "name": "offset", - "in": "query", - "required": false, - "description": "From which index to fetch the addresses", - "schema": { - "type": "number" - } - }, - { - "name": "count", - "in": "query", - "required": false, - "description": "Number of addresses to preview", - "schema": { - "type": "number" - } - } - ], - "description": "View addresses of a proposed payment method of the store", - "operationId": "StoreOnChainPaymentMethods_POSTOnChainPaymentMethodPreview", - "requestBody": { - "x-name": "request", - "content": { - "application/json": { - "schema": { - "type": "object", - "properties": { - "derivationScheme": { - "type": "string", - "description": "The derivation scheme", - "example": "xpub..." - } - } - } - } - }, - "required": true, - "x-position": 1 - }, - "responses": { - "200": { - "description": "specified payment method addresses", - "content": { - "application/json": { - "schema": { - "$ref": "#/components/schemas/OnChainPaymentMethodPreviewResultData" - } - } - } - }, - "403": { - "description": "If you are authenticated but forbidden to view the specified store" - }, - "404": { - "description": "The key is not found for this store" - } - }, - "security": [ - { - "API_Key": [ - "btcpay.store.canviewstoresettings" - ], - "Basic": [] - } - ] - } - } - }, - "components": { - "schemas": { - "Mnemonic": { - "type": "string", - "description": "A BIP39 mnemonic", - "example": "quality warfare scatter zone report forest potato local swing solve upon candy garment boost lab" - }, - "OnChainPaymentMethodBaseData": { - "type": "object", - "additionalProperties": false, - "properties": { - "derivationScheme": { - "type": "string", - "description": "The derivation scheme", - "example": "xpub..." - }, - "label": { - "type": "string", - "description": "A label that will be shown in the UI" - }, - "accountKeyPath": { - "type": "string", - "description": "The wallet fingerprint followed by the keypath to derive the account key used for signing operation or creating PSBTs", - "example": "abcd82a1/84'/0'/0'" - } - } - }, - "OnChainPaymentMethodDataPreview": { - "allOf": [ - { - "$ref": "#/components/schemas/OnChainPaymentMethodBaseData" - }, - { - "type": "object", - "properties": { - "cryptoCode": { - "type": "string", - "description": "Crypto code of the payment method" - } - } - } - ] - }, - "OnChainPaymentMethodPreviewResultData": { - "type": "object", - "additionalProperties": false, - "properties": { - "addresses": { - "type": "array", - "description": "a list of addresses generated by the derivation scheme", - "items": { - "$ref": "#/components/schemas/OnChainPaymentMethodPreviewResultAddressItem" - } - } - } - }, - "OnChainPaymentMethodPreviewResultAddressItem": { - "type": "object", - "additionalProperties": false, - "properties": { - "keyPath": { - "type": "string", - "description": "The key path relative to the account key path." - }, - "address": { - "type": "string", - "description": "The address generated at the key path" - } - } - }, - "GenerateOnChainWalletRequest": { - "type": "object", - "additionalProperties": false, - "properties": { - "label": { - "type": "string", - "description": "A label that will be shown in the UI" - }, - "existingMnemonic": { - "$ref": "#/components/schemas/Mnemonic" - }, - "passphrase": { - "type": "string", - "description": "A passphrase for the BIP39 mnemonic seed" - }, - "accountNumber": { - "type": "number", - "default": 0, - "description": "The account to derive from the BIP39 mnemonic seed" - }, - "savePrivateKeys": { - "type": "boolean", - "default": false, - "description": "Whether to store the seed inside BTCPay Server to enable some additional services. IF `false` AND `existingMnemonic` IS NOT SPECIFIED, BE SURE TO SECURELY STORE THE SEED IN THE RESPONSE!" - }, - "importKeysToRPC": { - "type": "boolean", - "default": false, - "description": "Whether to import all addresses generated via BTCPay Server into the underlying node wallet. (Private keys will also be imported if `savePrivateKeys` is set to true." - }, - "wordList": { - "type": "string", - "description": "If `existingMnemonic` is not set, a mnemonic is generated using the specified wordList.", - "default": "English", - "x-enumNames": [ - "English", - "Japanese", - "Spanish", - "ChineseSimplified", - "ChineseTraditional", - "French", - "PortugueseBrazil", - "Czech" - ], - "enum": [ - "English", - "Japanese", - "Spanish", - "ChineseSimplified", - "ChineseTraditional", - "French", - "PortugueseBrazil", - "Czech" - ] - }, - "wordCount": { - "type": "number", - "description": "If `existingMnemonic` is not set, a mnemonic is generated using the specified wordCount.", - "default": 12, - "x-enumNames": [ - 12, - 15, - 18, - 21, - 24 - ], - "enum": [ - 12, - 15, - 18, - 21, - 24 - ] - }, - "scriptPubKeyType": { - "type": "string", - "description": "the type of wallet to generate", - "default": "Segwit", - "x-enumNames": [ - "Legacy", - "Segwit", - "SegwitP2SH", - "TaprootBIP86" - ], - "enum": [ - "Legacy", - "Segwit", - "SegwitP2SH", - "TaprootBIP86" - ] - } - } - } - } - }, - "tags": [ - { - "name": "Store Payment Methods (On Chain)", - "description": "Store Payment Methods (On Chain) operations" - } - ] -} diff --git a/BTCPayServer/wwwroot/swagger/v1/swagger.template.stores-wallet.on-chain.json b/BTCPayServer/wwwroot/swagger/v1/swagger.template.stores-wallet.on-chain.json index ddc94376d..1fb24b69e 100644 --- a/BTCPayServer/wwwroot/swagger/v1/swagger.template.stores-wallet.on-chain.json +++ b/BTCPayServer/wwwroot/swagger/v1/swagger.template.stores-wallet.on-chain.json @@ -1,6 +1,6 @@ { "paths": { - "/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet": { + "/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet": { "get": { "tags": [ "Store Wallet (On Chain)" @@ -17,14 +17,7 @@ } }, { - "name": "cryptoCode", - "in": "path", - "required": true, - "description": "The crypto code of the payment method to fetch", - "schema": { - "type": "string" - }, - "example": "BTC" + "$ref": "#/components/parameters/PaymentMethodId" } ], "description": "View information about the specified wallet", @@ -57,7 +50,7 @@ ] } }, - "/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/feerate": { + "/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/feerate": { "get": { "tags": [ "Store Wallet (On Chain)" @@ -74,14 +67,7 @@ } }, { - "name": "cryptoCode", - "in": "path", - "required": true, - "description": "The crypto code of the payment method to fetch", - "schema": { - "type": "string" - }, - "example": "BTC" + "$ref": "#/components/parameters/PaymentMethodId" }, { "name": "blockTarget", @@ -124,7 +110,7 @@ ] } }, - "/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/address": { + "/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/address": { "get": { "tags": [ "Store Wallet (On Chain)" @@ -141,14 +127,7 @@ } }, { - "name": "cryptoCode", - "in": "path", - "required": true, - "description": "The crypto code of the payment method to fetch", - "schema": { - "type": "string" - }, - "example": "BTC" + "$ref": "#/components/parameters/PaymentMethodId" }, { "name": "forceGenerate", @@ -206,14 +185,7 @@ } }, { - "name": "cryptoCode", - "in": "path", - "required": true, - "description": "The crypto code of the payment method to fetch", - "schema": { - "type": "string" - }, - "example": "BTC" + "$ref": "#/components/parameters/PaymentMethodId" } ], "description": "UnReserve address", @@ -239,7 +211,7 @@ ] } }, - "/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions": { + "/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/transactions": { "get": { "tags": [ "Store Wallet (On Chain)" @@ -256,14 +228,7 @@ } }, { - "name": "cryptoCode", - "in": "path", - "required": true, - "description": "The crypto code of the wallet to fetch", - "schema": { - "type": "string" - }, - "example": "BTC" + "$ref": "#/components/parameters/PaymentMethodId" }, { "name": "statusFilter", @@ -357,14 +322,7 @@ } }, { - "name": "cryptoCode", - "in": "path", - "required": true, - "description": "The crypto code of the wallet", - "schema": { - "type": "string" - }, - "example": "BTC" + "$ref": "#/components/parameters/PaymentMethodId" } ], "requestBody": { @@ -415,7 +373,7 @@ ] } }, - "/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/transactions/{transactionId}": { + "/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/transactions/{transactionId}": { "get": { "tags": [ "Store Wallet (On Chain)" @@ -432,14 +390,7 @@ } }, { - "name": "cryptoCode", - "in": "path", - "required": true, - "description": "The crypto code of the wallet to fetch", - "schema": { - "type": "string" - }, - "example": "BTC" + "$ref": "#/components/parameters/PaymentMethodId" }, { "name": "transactionId", @@ -496,14 +447,7 @@ } }, { - "name": "cryptoCode", - "in": "path", - "required": true, - "description": "The crypto code of the wallet to fetch", - "schema": { - "type": "string" - }, - "example": "BTC" + "$ref": "#/components/parameters/PaymentMethodId" }, { "name": "transactionId", @@ -564,7 +508,7 @@ ] } }, - "/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/utxos": { + "/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/utxos": { "get": { "tags": [ "Store Wallet (On Chain)" @@ -581,14 +525,7 @@ } }, { - "name": "cryptoCode", - "in": "path", - "required": true, - "description": "The crypto code of the wallet to fetch", - "schema": { - "type": "string" - }, - "example": "BTC" + "$ref": "#/components/parameters/PaymentMethodId" } ], "description": "Get store on-chain wallet utxos", @@ -623,10 +560,402 @@ } ] } + }, + "/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/generate": { + "post": { + "tags": [ + "Store Wallet (On Chain)" + ], + "summary": "Generate store on-chain wallet", + "parameters": [ + { + "name": "storeId", + "in": "path", + "required": true, + "description": "The store to fetch", + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/PaymentMethodId" + } + ], + "description": "Generate a wallet and update the specified store's payment method to it", + "requestBody": { + "x-name": "request", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/GenerateOnChainWalletRequest" + } + } + }, + "required": true, + "x-position": 1 + }, + "operationId": "StoreOnChainPaymentMethods_GenerateOnChainWallet", + "responses": { + "200": { + "description": "updated specified payment method with the generated wallet", + "content": { + "application/json": { + "schema": { + "allOf": [ + { + "$ref": "#/components/schemas/GenericPaymentMethodData" + }, + { + "type": "object", + "properties": { + "mnemonic": { + "$ref": "#/components/schemas/Mnemonic" + }, + "config": { + "$ref": "#/components/schemas/OnChainPaymentMethodBaseData" + } + } + } + ] + } + } + } + }, + "400": { + "description": "A list of errors that occurred when updating the store payment method", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/ValidationProblemDetails" + } + } + } + }, + "403": { + "description": "If you are authenticated but forbidden to update the specified store" + }, + "404": { + "description": "The key is not found for this store" + } + }, + "security": [ + { + "API_Key": [ + "btcpay.store.canmodifystoresettings" + ], + "Basic": [] + } + ] + } + }, + "/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/preview": { + "get": { + "tags": [ + "Store Wallet (On Chain)" + ], + "summary": "Preview store on-chain payment method addresses", + "parameters": [ + { + "name": "storeId", + "in": "path", + "required": true, + "description": "The store to fetch", + "schema": { + "type": "string" + } + }, + { + "name": "paymentMethodId", + "in": "path", + "required": true, + "description": "The payment method id of the payment method to update", + "schema": { + "type": "string" + }, + "example": "BTC-CHAIN" + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "From which index to fetch the addresses", + "schema": { + "type": "number" + } + }, + { + "name": "count", + "in": "query", + "required": false, + "description": "Number of addresses to preview", + "schema": { + "type": "number" + } + } + ], + "description": "View addresses of the current payment method of the store", + "operationId": "StoreOnChainPaymentMethods_GetOnChainPaymentMethodPreview", + "responses": { + "200": { + "description": "specified payment method addresses", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OnChainPaymentMethodPreviewResultData" + } + } + } + }, + "403": { + "description": "If you are authenticated but forbidden to view the specified store" + }, + "404": { + "description": "The key is not found for this store/payment method" + } + }, + "security": [ + { + "API_Key": [ + "btcpay.store.canviewstoresettings" + ], + "Basic": [] + } + ] + }, + "post": { + "tags": [ + "Store Wallet (On Chain)" + ], + "summary": "Preview proposed store on-chain payment method addresses", + "parameters": [ + { + "name": "storeId", + "in": "path", + "required": true, + "description": "The store to fetch", + "schema": { + "type": "string" + } + }, + { + "$ref": "#/components/parameters/PaymentMethodId" + }, + { + "name": "offset", + "in": "query", + "required": false, + "description": "From which index to fetch the addresses", + "schema": { + "type": "number" + } + }, + { + "name": "count", + "in": "query", + "required": false, + "description": "Number of addresses to preview", + "schema": { + "type": "number" + } + } + ], + "description": "View addresses of a proposed payment method of the store", + "operationId": "StoreOnChainPaymentMethods_POSTOnChainPaymentMethodPreview", + "requestBody": { + "x-name": "request", + "content": { + "application/json": { + "schema": { + "type": "object", + "properties": { + "derivationScheme": { + "type": "string", + "description": "The derivation scheme", + "example": "xpub..." + } + } + } + } + }, + "required": true, + "x-position": 1 + }, + "responses": { + "200": { + "description": "specified payment method addresses", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/OnChainPaymentMethodPreviewResultData" + } + } + } + }, + "403": { + "description": "If you are authenticated but forbidden to view the specified store" + }, + "404": { + "description": "The key is not found for this store" + } + }, + "security": [ + { + "API_Key": [ + "btcpay.store.canviewstoresettings" + ], + "Basic": [] + } + ] + } } }, "components": { "schemas": { + "Mnemonic": { + "type": "string", + "description": "A BIP39 mnemonic", + "example": "quality warfare scatter zone report forest potato local swing solve upon candy garment boost lab" + }, + "OnChainPaymentMethodBaseData": { + "type": "object", + "additionalProperties": false, + "properties": { + "derivationScheme": { + "type": "string", + "description": "The derivation scheme", + "example": "xpub..." + }, + "label": { + "type": "string", + "description": "A label that will be shown in the UI" + }, + "accountKeyPath": { + "type": "string", + "description": "The wallet fingerprint followed by the keypath to derive the account key used for signing operation or creating PSBTs", + "example": "abcd82a1/84'/0'/0'" + } + } + }, + "OnChainPaymentMethodPreviewResultData": { + "type": "object", + "additionalProperties": false, + "properties": { + "addresses": { + "type": "array", + "description": "a list of addresses generated by the derivation scheme", + "items": { + "$ref": "#/components/schemas/OnChainPaymentMethodPreviewResultAddressItem" + } + } + } + }, + "OnChainPaymentMethodPreviewResultAddressItem": { + "type": "object", + "additionalProperties": false, + "properties": { + "keyPath": { + "type": "string", + "description": "The key path relative to the account key path." + }, + "address": { + "type": "string", + "description": "The address generated at the key path" + } + } + }, + "GenerateOnChainWalletRequest": { + "type": "object", + "additionalProperties": false, + "properties": { + "label": { + "type": "string", + "description": "A label that will be shown in the UI" + }, + "existingMnemonic": { + "$ref": "#/components/schemas/Mnemonic" + }, + "passphrase": { + "type": "string", + "description": "A passphrase for the BIP39 mnemonic seed" + }, + "accountNumber": { + "type": "number", + "default": 0, + "description": "The account to derive from the BIP39 mnemonic seed" + }, + "savePrivateKeys": { + "type": "boolean", + "default": false, + "description": "Whether to store the seed inside BTCPay Server to enable some additional services. IF `false` AND `existingMnemonic` IS NOT SPECIFIED, BE SURE TO SECURELY STORE THE SEED IN THE RESPONSE!" + }, + "importKeysToRPC": { + "type": "boolean", + "default": false, + "description": "Whether to import all addresses generated via BTCPay Server into the underlying node wallet. (Private keys will also be imported if `savePrivateKeys` is set to true." + }, + "wordList": { + "type": "string", + "description": "If `existingMnemonic` is not set, a mnemonic is generated using the specified wordList.", + "default": "English", + "x-enumNames": [ + "English", + "Japanese", + "Spanish", + "ChineseSimplified", + "ChineseTraditional", + "French", + "PortugueseBrazil", + "Czech" + ], + "enum": [ + "English", + "Japanese", + "Spanish", + "ChineseSimplified", + "ChineseTraditional", + "French", + "PortugueseBrazil", + "Czech" + ] + }, + "wordCount": { + "type": "number", + "description": "If `existingMnemonic` is not set, a mnemonic is generated using the specified wordCount.", + "default": 12, + "x-enumNames": [ + 12, + 15, + 18, + 21, + 24 + ], + "enum": [ + 12, + 15, + 18, + 21, + 24 + ] + }, + "scriptPubKeyType": { + "type": "string", + "description": "the type of wallet to generate", + "default": "Segwit", + "x-enumNames": [ + "Legacy", + "Segwit", + "SegwitP2SH", + "TaprootBIP86" + ], + "enum": [ + "Legacy", + "Segwit", + "SegwitP2SH", + "TaprootBIP86" + ] + } + } + }, "OnChainWalletOverviewData": { "type": "object", "additionalProperties": false, @@ -679,20 +1008,6 @@ } } }, - "OnChainPaymentMethodPreviewResultAddressItem": { - "type": "object", - "additionalProperties": false, - "properties": { - "keyPath": { - "type": "string", - "description": "The key path relative to the account key path." - }, - "address": { - "type": "string", - "description": "The address generated at the key path" - } - } - }, "TransactionStatus": { "type": "string", "x-enumNames": [ @@ -754,7 +1069,7 @@ }, "timestamp": { "description": "The time of the transaction", - "allOf": [ {"$ref": "#/components/schemas/UnixTimestamp"}] + "allOf": [ { "$ref": "#/components/schemas/UnixTimestamp" } ] }, "status": { "allOf": [ @@ -798,7 +1113,7 @@ }, "timestamp": { "description": "The time of the utxo", - "allOf": [ {"$ref": "#/components/schemas/UnixTimestamp"}] + "allOf": [ { "$ref": "#/components/schemas/UnixTimestamp" } ] }, "keyPath": { "type": "string", diff --git a/BTCPayServer/wwwroot/swagger/v1/swagger.template.stores-wallet.on-chain.objects.json b/BTCPayServer/wwwroot/swagger/v1/swagger.template.stores-wallet.on-chain.objects.json index f91b9d1ee..f042d6805 100644 --- a/BTCPayServer/wwwroot/swagger/v1/swagger.template.stores-wallet.on-chain.objects.json +++ b/BTCPayServer/wwwroot/swagger/v1/swagger.template.stores-wallet.on-chain.objects.json @@ -1,6 +1,6 @@ { "paths": { - "/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects": { + "/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/objects": { "get": { "tags": [ "Store Wallet (On Chain)" @@ -17,14 +17,7 @@ } }, { - "name": "cryptoCode", - "in": "path", - "required": true, - "description": "The crypto code of the payment method to fetch", - "schema": { - "type": "string" - }, - "example": "BTC" + "$ref": "#/components/parameters/PaymentMethodId" }, { "name": "type", @@ -50,7 +43,10 @@ "in": "query", "required": false, "description": "Whether or not you should include neighbour's node data in the result (ie, `links.objectData`)", - "schema": { "type": "boolean", "default": true } + "schema": { + "type": "boolean", + "default": true + } } ], "description": "View wallet objects", @@ -101,14 +97,7 @@ } }, { - "name": "cryptoCode", - "in": "path", - "required": true, - "description": "The crypto code of the payment method to fetch", - "schema": { - "type": "string" - }, - "example": "BTC" + "$ref": "#/components/parameters/PaymentMethodId" } ], "description": "Add/Update wallet objects", @@ -151,7 +140,7 @@ ] } }, - "/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects/{objectType}/{objectId}": { + "/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/objects/{objectType}/{objectId}": { "get": { "tags": [ "Store Wallet (On Chain)" @@ -168,14 +157,7 @@ } }, { - "name": "cryptoCode", - "in": "path", - "required": true, - "description": "The crypto code of the payment method to fetch", - "schema": { - "type": "string" - }, - "example": "BTC" + "$ref": "#/components/parameters/PaymentMethodId" }, { "name": "objectType", @@ -253,14 +235,7 @@ } }, { - "name": "cryptoCode", - "in": "path", - "required": true, - "description": "The crypto code of the payment method to fetch", - "schema": { - "type": "string" - }, - "example": "BTC" + "$ref": "#/components/parameters/PaymentMethodId" }, { "name": "objectType", @@ -306,7 +281,7 @@ ] } }, - "/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects/{objectType}/{objectId}/links": { + "/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/objects/{objectType}/{objectId}/links": { "post": { "tags": [ "Store Wallet (On Chain)" @@ -323,14 +298,7 @@ } }, { - "name": "cryptoCode", - "in": "path", - "required": true, - "description": "The crypto code of the payment method to fetch", - "schema": { - "type": "string" - }, - "example": "BTC" + "$ref": "#/components/parameters/PaymentMethodId" }, { "name": "objectType", @@ -386,7 +354,7 @@ ] } }, - "/api/v1/stores/{storeId}/payment-methods/onchain/{cryptoCode}/wallet/objects/{objectType}/{objectId}/links/{linkType}/{linkId}": { + "/api/v1/stores/{storeId}/payment-methods/{paymentMethodId}/wallet/objects/{objectType}/{objectId}/links/{linkType}/{linkId}": { "delete": { "tags": [ "Store Wallet (On Chain)" @@ -403,14 +371,7 @@ } }, { - "name": "cryptoCode", - "in": "path", - "required": true, - "description": "The crypto code of the payment method to fetch", - "schema": { - "type": "string" - }, - "example": "BTC" + "$ref": "#/components/parameters/PaymentMethodId" }, { "name": "objectType",