Skip to content

Add C# language bindings for payjoin-ffi#1318

Merged
spacebear21 merged 5 commits intopayjoin:masterfrom
chavic:chavic/c-sharp-bindings
Feb 20, 2026
Merged

Add C# language bindings for payjoin-ffi#1318
spacebear21 merged 5 commits intopayjoin:masterfrom
chavic:chavic/c-sharp-bindings

Conversation

@chavic
Copy link
Collaborator

@chavic chavic commented Feb 9, 2026

This work in progress adds C# bindings and implements tests following the pattern in the other languages

@coveralls
Copy link
Collaborator

coveralls commented Feb 9, 2026

Pull Request Test Coverage Report for Build 22152746445

Details

  • 0 of 0 changed or added relevant lines in 0 files are covered.
  • No unchanged relevant lines lost coverage.
  • Overall coverage remained the same at 83.169%

Totals Coverage Status
Change from base Build 22148988412: 0.0%
Covered Lines: 10219
Relevant Lines: 12287

💛 - Coveralls

@chavic chavic force-pushed the chavic/c-sharp-bindings branch 2 times, most recently from b4799da to ae74db7 Compare February 9, 2026 12:14
@DanGould
Copy link
Contributor

DanGould commented Feb 9, 2026

BOOYEAH

I like the direction this is going.

<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is net8.0 the most common target? I'm very curious what the actual needs of a BTCpayServer plugin or Strike are, because I've dealt with incompatibility issue here, albeit years ago.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it can: https://blog.btcpayserver.org/btcpay-server-1-12-0/#upgrade-to-net-8. Strike is likely ahead because they don't need to support a bunch of open source stuff.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I see, any idea which specific runtime they're on?

I did a quick check beforehand to target the most common runtime right now...

Gonna look loser into the Strike repos specifically

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

strike very likely on net8.0. probably private repos. i'll ask their team

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Confirmed net8.0 for them and BTCpayServer, and both likely upgrade to net10.0 very soon.

@ValeraFinebits
Copy link
Contributor

I've been working on an experimental C# bindings prototype in my fork (ValeraFinebits#1) while building a BTCPayServer Payjoin plugin, mainly to explore integration possibilities.

This PR aligns closely with what I implemented there, and in my local testing everything works as expected. I'll migrate my plugin to these upstream bindings and share any issues or improvement ideas as I go.

@chavic chavic force-pushed the chavic/c-sharp-bindings branch from ae74db7 to 4e75d20 Compare February 12, 2026 15:34
@DanGould
Copy link
Contributor

Hi @ValeraFinebits I saw your PR open and close. Thanks bigly for writing us updates here. Very eager to see what that plugin can do and challenges hooking it up to BTCPay via the plugin framework.

@chavic chavic force-pushed the chavic/c-sharp-bindings branch 2 times, most recently from ef26b10 to 769f0a9 Compare February 15, 2026 13:22
@chavic chavic requested a review from spacebear21 February 15, 2026 13:41
@chavic chavic self-assigned this Feb 15, 2026
@chavic chavic requested a review from DanGould February 15, 2026 13:41
@chavic chavic marked this pull request as ready for review February 15, 2026 13:43
Copy link
Contributor

@DanGould DanGould left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. What's the plan with the dependency on your personal fork? i'm OK with that with giant, blaring ⚠️, even better would be in the payjoin project, but ultimately we want to upstream whatever fix was necessary. The process of making the PR also serves as documenting what changed. I don't know what changed at all in this PR other than the fact that it's forked. On this note, I'd much rather pin a commit hash than a branch so we have exact dependency that can't drift.
  2. README references chavic/external-types-support but the script installs chavic/csharp-handle-fix. One of them is stale. Prefer commit & to doc in tested code > README in part for this reason
  3. Missing async persister tests (compared to other bindings) Python and Dart both test JsonReceiverSessionPersisterAsync / JsonSenderSessionPersisterAsync. C# only tests the sync path. Is async support not generated yet,
    or just untested?
  4. No Windows CI
    C# is disproportionately a Windows ecosystem language. BTCPayServer runs on Windows. The matrix only has ubuntu-latest and macos-latest. Is this a known
    limitation or an oversight? I do see this changing, even NicolasDorier BTCPay Emperor has been tweeting about giving up windows, but C# specifically might deserve a windows flow.
  5. Why brew install llvm on macOS?
    The other bindings (Python, Dart) don't need this. Is it required for uniffi-bindgen-cs compilation, or the native lib? Should be documented.

<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need unsafe? known lifetime gotchas? Ideally we'd NOT do this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, AllowUnsafeBlocks is currently required for the generated interop code (pointer usage in generated C#). I added an inline comment in the project file documenting this and noting that disabling /unsafe fails with CS0227. I'll revisit this to see if there's a way to avoid it

Copy link
Contributor

@DanGould DanGould left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We probably need nuspec too. We've got the Payjoin handle https://www.nuget.org/packages/Payjoin/

@chavic
Copy link
Collaborator Author

chavic commented Feb 16, 2026

  1. What's the plan with the dependency on your personal fork? i'm OK with that with giant, blaring ⚠️, even better would be in the payjoin project, but ultimately we want to upstream whatever fix was necessary. The process of making the PR also serves as documenting what changed. I don't know what changed at all in this PR other than the fact that it's forked. On this note, I'd much rather pin a commit hash than a branch so we have exact dependency that can't drift.
  2. README references chavic/external-types-support but the script installs chavic/csharp-handle-fix. One of them is stale. Prefer commit & to doc in tested code > README in part for this reason
  3. Missing async persister tests (compared to other bindings) Python and Dart both test JsonReceiverSessionPersisterAsync / JsonSenderSessionPersisterAsync. C# only tests the sync path. Is async support not generated yet,
    or just untested?
  4. No Windows CI
    C# is disproportionately a Windows ecosystem language. BTCPayServer runs on Windows. The matrix only has ubuntu-latest and macos-latest. Is this a known
    limitation or an oversight? I do see this changing, even NicolasDorier BTCPay Emperor has been tweeting about giving up windows, but C# specifically might deserve a windows flow.
  5. Why brew install llvm on macOS?
    The other bindings (Python, Dart) don't need this. Is it required for uniffi-bindgen-cs compilation, or the native lib? Should be documented.

Going to make an upstream PR after this works. Could do a draft for documentation.

Addressing the rest in upcoming commits.

@chavic chavic force-pushed the chavic/c-sharp-bindings branch 2 times, most recently from 6646f19 to bf7ea11 Compare February 17, 2026 20:14
@chavic chavic force-pushed the chavic/c-sharp-bindings branch 2 times, most recently from eb9f897 to b859525 Compare February 18, 2026 18:38
@chavic
Copy link
Collaborator Author

chavic commented Feb 18, 2026

Addressed most of the issues mentioned by @DanGould I think this PR it's ready for a second look. @spacebear21 @ValeraFinebits

@ValeraFinebits
Copy link
Contributor

Addressed most of the issues mentioned by @DanGould I think this PR it's ready for a second look. @spacebear21 @ValeraFinebits

Windows now works fine. 👍

@DanGould
Copy link
Contributor

Stoked this is working on Windows now.

On very first blush I see your new commits are telling me what happened but not why. The rationale is supercritical. Don't forget! Why > What!

@DanGould
Copy link
Contributor

The changes you made to uniffi-bindgen-cs look sane, so I'd like to see them as open PRs there right away so we can get feedback ASAP. They can be one or multiple PRs but I think the feedback there is the most valuable next step we could have.

Copy link
Contributor

@DanGould DanGould left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is pretty much ready to land as an expreiment but I would want @spacebear21's review and I really want to see open PRs upstream so we know we're on the right path.

It would be really nice to have a csharp devshell. I think we could also remove dart from the top level flake and make a devshell so that it doesn't pollute the top level with 300mb of stuff not everyone needs as well which could be done at the same time as as a follow up

  csharpDevShell = pkgs.mkShell {                                                                                                                                    
    name = "csharp-dev";                                                                                                                                             
    buildInputs = with pkgs; [                                                                                                                                       
      dotnet-sdk_8                                                                                                                                                   
      rustToolchains.msrv                                                                                                                                            
    ];                                                                                                                                                               
    shellHook = ''                                                                                                                                                   
      cd payjoin-ffi/csharp                                                                                                                                          
    '';                                                                                                                                                              
  };                                                                                                                                                                 
                                                                                                                                                                     
  # then in devShells:                                                                                                                                               
  devShells = devShells // {                                                                                                                                         
    default = devShells.nightly;                                                                                                                                     
    csharp = csharpDevShell;                                                                                                                                         
    # python = pythonDevShell;  # from PR #864                                                                                                                       
  };     

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok so I see now that without this commit, cargo build fails on Windows. Since the C# CI matrix includes windows-latest, this commit unblocks compilation.

This took me about 20 minutes of review to confirm and could have been alleviated by a simple commit message. Next time I will have to refuse to review commits without rationale as part of their message.

Comment on lines +19 to +20
# Run all tests
dotnet test
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be nice to mirror the same pattern as other repos with a contrib/test.sh

  #!/usr/bin/env bash                                                                                                                                                
  set -euo pipefail

  SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
  cd "$SCRIPT_DIR/.."

  bash ./scripts/generate_bindings.sh
  dotnet test --logger "console;verbosity=minimal"

@chavic
Copy link
Collaborator Author

chavic commented Feb 20, 2026

Stoked this is working on Windows now.

On very first blush I see your new commits are telling me what happened but not why. The rationale is supercritical. Don't forget! Why > What!

Will do

Copy link
Collaborator

@spacebear21 spacebear21 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

utACK b859525 (didn't test locally but CI looks good on all platforms)

+1 on writing explanatory commit descriptions for why each change is made. Other than that the commits organization was nice and easy to follow.

Some thoughts on follow-ups:

  • Add integration tests for the end-to-end payjoin flow like we have in the other downstream languages.
  • Although I started the trend for dart to include 3rd party bindings generation in payjoin-ffi by hijacking uniffi-bindgen.rs, I think it will be more manageable as we add more languages to remove those optional dependencies/features from payjoin-ffi, and instead silo each language's generator inside that language's directory e.g. by using a cli generator like we do for javascript currently. Notably, adding these optional dependencies still inflates the main Cargo.lock files which can lead to dependency conflicts, MSRV issues, etc.

@spacebear21 spacebear21 merged commit 1a7effa into payjoin:master Feb 20, 2026
23 checks passed
@ValeraFinebits
Copy link
Contributor

utACK b859525 (didn't test locally but CI looks good on all platforms)

+1 on writing explanatory commit descriptions for why each change is made. Other than that the commits organization was nice and easy to follow.

Some thoughts on follow-ups:

  • Add integration tests for the end-to-end payjoin flow like we have in the other downstream languages.
  • Although I started the trend for dart to include 3rd party bindings generation in payjoin-ffi by hijacking uniffi-bindgen.rs, I think it will be more manageable as we add more languages to remove those optional dependencies/features from payjoin-ffi, and instead silo each language's generator inside that language's directory e.g. by using a cli generator like we do for javascript currently. Notably, adding these optional dependencies still inflates the main Cargo.lock files which can lead to dependency conflicts, MSRV issues, etc.

Integration tests have been added here:
#1350

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants