How to add custom bitcoin signet to NBitcoin and NBXplorer


While developing a backend for exchange website for one small altcoin based on bitcoin I stumbled upon a problem: how to test all edge cases without spending real money and having to wait 10-60 minutes for mainnet to process my test transactions? I heard about testnet and used it before with Monero — it works awesome there, you can just grab 1 xmr for free without any problems. Bitcoin is completely different story.
Testnet
Testnet3/testnet4 are bitcoin blockchains where coins have no real value. This however won't stop many... This network is very unpredictable: blocks can come every 6-12 hours or every minute depending on how many miners are working on it. And if that was the only problem... The only bitcoin testnet faucet I found was extremely limited to like 4 dollars. And you had to solve a captcha to get your test bitcoins. Even after that you'd have to wait for hours for them to arrive. The testnet blockchain size is more than a few gigabytes and I think it's just a waste considering how bad it is.
Signet
The next one ChatGPT recommended to me was signet — it's basically the same thing except only special preapproved people can mine blocks, thus making this network very predictable (blocks come every 10 minutes). Surely there would be more faucets, right? No, the only faucet I found was giving you at most 0.01 test btc and it was probably made by some script kiddy having weird captions and don't you even dare to think to request more than 0.01 "no real value coin" per day because it will throw you a humiliating error on 20 languages about how you are wasting everyone's time. I guess that's normal for crypto community but whatever. Clearly signet is still a piece of junk so moving on to...
Custom signet
At this point why not just run your own blockchain? You can control when blocks are mined, how fast they're mined and you can have millions of worthless tokens to test transactions. On top of that the blockchain is 10-20 megabytes. Isn't that cool?
I spent 4 hours trying to setup custom signet, turns out the semi-official wiki was out of date. I found this awesome guide by Edil Medeiros and finally got it working. I already ran btcpayserver+nbxplorer+bitcoind with testnet and signet and I genuinely thought changing signetchallenge will just isolate my network from the official one — and that's it, btcpay will just work™.
That's when my first painful bottleneck in months showed up:
The node is not in a connected state
So I searched all over the internet, I searched in GitHub issues but I could not find a working solution for this, the error was the same but the reason for it was something else.
That's when I realized I need to dig into source code of NBXplorer to find where this error comes from. I'm not a C# developer and honestly couldn't believe someone still uses this relic language outside of Microsoft. Luckily, I found that this error was thrown in ReceivePayload function called in NBXplorer.Indexer.BTC. More specifically it was thrown because the node class instance was connected but nbitcoin could not talk to it.
Then I realized I have seen this behaviour already when trying to use regtest bitcoin daemon as signet or forgetting -signet flag and connecting to signet node as mainnet. I found that people usually reported this issue with new altcoins. Additionally this error only happened with my custom signet, not with the official signet, so I knew it must be the problem.
The solution
NBitcoin
First, I forked NBitcoin and copied NBitcoin/Bitcoin.Signet.cs file. I renamed it to the name of my mocknet, changed all internal names of methods so that they don't conflict with the existing Signet class and, most importantly, changed the challengeBytes string in GetSignetMagic. This value is the same thing you'd put in signetchallenge property in bitcoin daemon config.
Technically you can just change GetSignetMagic value of the Bitcoin.Signet.cs file but I don't recommend modifying existing coins files.
Next, I added this line to NBitcoin.Tests/NetworkTests.cs:
1Assert.Equal(Network.GetNetwork("mysignet"), Bitcoin.Instance.Mysignet);
And finally I added this line to NBitcoin/Bitcoin.cs Init method:
1CreateMysignet();
That was it for NBitcoin, now just build it and it should be ready to connect to your signet. You'll need to install .NET on your computer, preferrably .NET 8.0 as this is what NBXplorer requires in the next step.
1dotnet build -c Release
NBXplorer
In order to use this network you still need to modify NBXplorer's code, so let's fork it and add support for the custom signet there too!
We actually only need to add one line of code in NBXplorer/Configuration/DefaultConfiguration.cs:
1conf.GetOrDefault<bool>("mysignet", false) ? Bitcoin.Instance.Mysignet.ChainName :
Make sure the syntax is correct. Technically modifying this file isn't required because you'd normally use --network argument with the name of your signet instead of --mysignet flag on nbxplorer executable, but we need to link everything together anyway, so it's a good idea to change configuration file just in case.
Now we need to make NBXplorer use our custom modified version of NBitcoin. To force it to use local version rather than getting it as a dependency from nuget, you'll need to add ProjectReference tags to every csproj that imports NBitcoin.
NBXplorer.Client/NBXplorer.Client.csproj:
1// Replace:2<PackageReference Include="NBitcoin" Version="8.0.13" />3<PackageReference Include="NBitcoin.Altcoins" Version="4.0.8" />4// with:5<ProjectReference Include="..\..\NBitcoin\NBitcoin\NBitcoin.csproj" />6<ProjectReference Include="..\..\NBitcoin\NBitcoin.Altcoins\NBitcoin.Altcoins.csproj" />
NBXplorer.Tests/NBXplorer.Testss.csproj:
1// Replace:2<PackageReference Include="NBitcoin.TestFramework" Version="3.0.41" />3// with:4<ProjectReference Include="..\..\NBitcoin\NBitcoin.TestFramework\NBitcoin.TestFramework.csproj" />
Obviously these paths must correctly resolve to the corresponding files in NBitcoin directory on your computer. If you don't have NBitcoin repository placed directly near NBXplorer repository, you might need to change these paths. Keep in mind that the weird invertned windows "\" is supposed to be there, I ran this on macOS.
In your nbxplorer config file make sure to select your custom signet:
1network=mysignet
I also recommend double-checking the new location of .cookie file if you use it for btcpayserver to interact with nbxplorer.
Build it using ./build.sh script in the repository before moving on.
BTCPay Server
Finally, we want to fork btcpayserver and add our custom signet there too. Code modifications are optional but I'd still recommend to do them. It's the easiest part, trust me!
BTCPayServer/Configuration/DefaultConfiguration.cs:
1// Add this line among other cli options2app.Option("--mysignet | -mysignet", $"Use mysignet (deprecated, use --network instead)", CommandOptionType.BoolValue);
And then in GetNetworkType method in the same file:
1conf.GetOrDefault<bool>("mysignet", false) ? Bitcoin.Instance.Mysignet.ChainName :
BTCPayServer/Components/StoreSelector/Default.cshtml:
1// Replace:2var displayType = type.Replace("Testnet", "TN").Replace("Regtest", "RT").Replace("Signet", "SN");3// with:4var displayType = type.Replace("Testnet", "TN").Replace("Mysignet", "GG").Replace("Regtest", "RT").Replace("Signet", "SN");5// you can pick any two characters or longer string6// this is completely optional though as btcpayserver will simply7// display your chain internal name if you don't define replacement here
BTCPayServer/Plugins/Bitcoin/BitcoinPlugin.cs:
1// add to Execute method2chainName == NBitcoin.Bitcoin.Instance.Mysignet.ChainName ? "https://github.com/VityaSchel/my-custom-signet"3// I have no idea where this is used and I think you can skip this part4// if you don't have a mempool, but just in case I'd put a link to some5// kind of specification or description here
Now the most important part! You need to replace all links to NBitcoin and NBXplorer in csproj files.
BTCPayServer.Client/BTCPayServer.Client.csproj:
1// Replace:2<PackageReference Include="NBitcoin" Version="8.0.13" />3// with4<ProjectReference Include="..\..\NBitcoin\NBitcoin\NBitcoin.csproj" />
BTCPayServer.Common/BTCPayServer.Common.csproj:
1// Replace:2<PackageReference Include="NBXplorer.Client" Version="4.3.9" />3// with4<ProjectReference Include="..\..\NBXplorer\NBXplorer.Client\NBXplorer.Client.csproj" />
BTCPayServer.Data/BTCPayServer.Data.csproj:
1// Replace:2<PackageReference Include="NBitcoin.Altcoins" Version="4.0.8" />3// with4<ProjectReference Include="..\..\NBitcoin\NBitcoin.Altcoins\NBitcoin.Altcoins.csproj" />
BTCPayServer.Rating/BTCPayServer.Rating.csproj:
1// Replace:2<PackageReference Include="NBitcoin" Version="8.0.13" />3// with4<ProjectReference Include="..\..\NBitcoin\NBitcoin\NBitcoin.csproj" />
BTCPayServer/BTCPayServer.csproj:
1// Replace:2<PackageReference Include="NBitcoin" Version="8.0.13" />3// with:4<ProjectReference Include="..\..\NBitcoin\NBitcoin\NBitcoin.csproj" />
Thanks for reading this post and wish you all the best with bitcoin! Adding support for a bitcoin-based signet is the easiest part of creating your CEX; as of the time of writing this I'm struggling with implementing a heavily modified fork of bitcoin which uses custom cryptography into NBitcoin.. maybe I'll make a follow up post where I describe my struggles of writing C# code, but that's a story for another time.