Rename CryptoMarket rate provider to Notbank (#7526)
What changed, and why it matters
This commit renames BTCPay Server's exchange-rate source from 'CryptoMarket' to 'Notbank'. It swaps the old provider code for a new one that calls Notbank's API, and adds a database migration that rewrites existing store settings so they continue to use the new rate source name. There is no indication this fixes a security vulnerability; it appears to be a routine rebranding/integration change.
Treat as a routine provider rename. Verify that api.notbank.com is a trusted endpoint, that the new API's TLS certificate and availability are acceptable, and that the migration correctly preserves store rate-source selections. No security patch urgency is indicated.
Security signals we found
Third-party API endpoint changed from api.exchange.cryptomkt.com to api.notbank.com
New provider uses HTTP POST with empty JSON body instead of GET
Database migration performs in-place JSON text replacement for provider name
No authentication or secret handling visible in diff
Evidence from the diff
The change removes CryptoMarketExchangeRateProvider and introduces NotbankExchangeRateProvider, registered via AddRateProvider
Changed components
BTCPayServer.Rating/Providers/NotbankExchangeRateProvider.csBTCPayServer.Rating/Providers/CryptoMarketExchangeRateProvider.csBTCPayServer/Hosting/BTCPayServerServices.csBTCPayServer.Data/Migrations/20260828000000_rename_cryptomarket_notbank.csBTCPayServer.Tests/ThirdPartyTests.csInspect captured patch +74 / −54
### .agents/skills/btcpayserver-pr-descriptions/SKILL.md
@@ -21,6 +21,7 @@ Write pull request descriptions for the people who need to understand the change
- Mention limitations, compatibility concerns, or follow-up work if users or operators should know about them.
- Do not repeat a file-by-file or commit-by-commit summary that reviewers can already see in the diff.
- Do not describe purely internal implementation choices unless they affect how someone uses, deploys, reviews, or tests BTCPay Server.
+- Do not include routine verification commands or a `Verified:` section. Mention testing only when it explains a user-visible limitation, manual QA evidence, or the user explicitly asks for it.
## Greenfield API Changes
### BTCPayServer.Data/Migrations/20260828000000_rename_cryptomarket_notbank.cs
@@ -0,0 +1,26 @@
+using BTCPayServer.Data;
+using Microsoft.EntityFrameworkCore.Infrastructure;
+using Microsoft.EntityFrameworkCore.Migrations;
+
+namespace BTCPayServer.Data.Migrations;
+
+[DbContext(typeof(ApplicationDbContext))]
+[Migration("20260828000000_rename_cryptomarket_notbank")]
+public class rename_cryptomarket_notbank : Migration
+{
+ protected override void Up(MigrationBuilder migrationBuilder)
+ {
+ migrationBuilder.Sql("""
+ UPDATE "Stores"
+ SET "StoreBlob" = replace("StoreBlob"::text, 'cryptomarket', 'notbank')::jsonb
+ WHERE "StoreBlob"::text LIKE '%cryptomarket%';
+ """);
+
+ migrationBuilder.Sql("""
+ UPDATE "Settings"
+ SET "Value" = replace("Value"::text, 'cryptomarket', 'notbank')::jsonb
+ WHERE "Id" = 'BTCPayServer.Services.PoliciesSettings'
+ AND "Value"::text LIKE '%cryptomarket%';
+ """);
+ }
+}
### BTCPayServer.Rating/Providers/CryptoMarketExchangeRateProvider.cs
@@ -1,52 +0,0 @@
-using System.Collections.Generic;
-using System.Globalization;
-using System.Linq;
-using System.Net.Http;
-using System.Threading;
-using System.Threading.Tasks;
-using BTCPayServer.Rating;
-using Newtonsoft.Json.Linq;
-
-namespace BTCPayServer.Services.Rates
-{
- public class CryptoMarketExchangeRateProvider : IRateProvider
- {
- public RateSourceInfo RateSourceInfo => new("cryptomarket", "CryptoMarket", "https://api.exchange.cryptomkt.com/api/3/public/ticker/");
- private readonly HttpClient _httpClient;
- public CryptoMarketExchangeRateProvider(HttpClient httpClient)
- {
- _httpClient = httpClient ?? new HttpClient();
- }
-
-
- readonly List<string> SupportedPairs = new List<string>()
- {
- "BTCARS",
- "BTCCLP",
- "BTCBRL"
- };
-
- public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
- {
- using var response = await _httpClient.GetAsync("https://api.exchange.cryptomkt.com/api/3/public/ticker/", cancellationToken);
- var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
-
- return ((jobj as JObject) ?? new JObject())
- .Properties()
- .Where(p => SupportedPairs.Contains(p.Name))
- .Select(p => new PairRate(CurrencyPair.Parse(p.Name), CreateBidAsk(p)))
- .Where(p => p.BidAsk != null)
- .ToArray();
- }
- private static BidAsk CreateBidAsk(JProperty p)
- {
- if (p.Value["bid"]?.Type is JTokenType.Null || p.Value["ask"]?.Type is JTokenType.Null)
- return new BidAsk(decimal.Parse(p.Value["last"]!.Value<string>(), System.Globalization.NumberStyles.Any, CultureInfo.InvariantCulture));
- var bid = decimal.Parse(p.Value["bid"]!.Value<string>(), System.Globalization.NumberStyles.Any, CultureInfo.InvariantCulture);
- var ask = decimal.Parse(p.Value["ask"]!.Value<string>(), System.Globalization.NumberStyles.Any, CultureInfo.InvariantCulture);
- if (bid > ask)
- return null;
- return new BidAsk(bid, ask);
- }
- }
-}
### BTCPayServer.Rating/Providers/NotbankExchangeRateProvider.cs
@@ -0,0 +1,45 @@
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Net.Http;
+using System.Text;
+using System.Threading;
+using System.Threading.Tasks;
+using BTCPayServer.Rating;
+using Newtonsoft.Json.Linq;
+
+namespace BTCPayServer.Services.Rates
+{
+ public class NotbankExchangeRateProvider : IRateProvider
+ {
+ public RateSourceInfo RateSourceInfo => new("notbank", "Notbank", "https://api.notbank.com/AP/Ticker");
+ private readonly HttpClient _httpClient;
+
+ public NotbankExchangeRateProvider(HttpClient httpClient)
+ {
+ _httpClient = httpClient ?? new HttpClient();
+ }
+ public async Task<PairRate[]> GetRatesAsync(CancellationToken cancellationToken)
+ {
+ using var response = await _httpClient.PostAsync("https://api.notbank.com/AP/Ticker", new StringContent("{}", Encoding.UTF8, "application/json"), cancellationToken);
+ var jobj = await response.Content.ReadAsAsync<JObject>(cancellationToken);
+
+ return ((jobj as JObject) ?? new JObject())
+ .Properties()
+ .Select(p => CreatePairRate(p))
+ .Where(p => p is not null)
+ .Select(p => p!)
+ .ToArray();
+ }
+
+ private PairRate CreatePairRate(JProperty p)
+ {
+ if (!CurrencyPair.TryParse(p.Name, out var pair))
+ return null;
+ var lastPrice = p.Value["last_price"]?.Value<string>();
+ if (lastPrice is null || !decimal.TryParse(lastPrice, NumberStyles.Any, CultureInfo.InvariantCulture, out var lastPricev))
+ return null;
+ return new PairRate(pair, new BidAsk(lastPricev));
+ }
+ }
+}
### BTCPayServer.Tests/ThirdPartyTests.cs
@@ -192,7 +192,7 @@ public async Task CanQueryDirectProviders()
e => e.CurrencyPair == new CurrencyPair("BTC", "NGN") &&
e.BidAsk.Bid > 1.0m); // 1 BTC will always be more than 1 NGN
}
- else if (name == "cryptomarket")
+ else if (name == "notbank")
{
Assert.Contains(exchangeRates.ByExchange[name],
e => e.CurrencyPair == new CurrencyPair("BTC", "CLP") &&
### BTCPayServer/Hosting/BTCPayServerServices.cs
@@ -741,7 +741,7 @@ internal static void RegisterRateSources(IServiceCollection services)
services.AddRateProvider<BitcoinKenyaRateProvider>();
services.AddRateProvider<BitpayRateProvider>();
services.AddRateProvider<RipioExchangeProvider>();
- services.AddRateProvider<CryptoMarketExchangeRateProvider>();
+ services.AddRateProvider<NotbankExchangeRateProvider>();
services.AddRateProvider<BitflyerRateProvider>();
services.AddRateProvider<YadioRateProvider>();
services.AddRateProvider<BtcTurkRateProvider>();Why this scored 19/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.