Make the C# package README user-facing - #1764
Conversation
Coverage Report for CI Build 30827364559Coverage remained the same at 86.57%Details
Uncovered ChangesNo uncovered changes found. Coverage RegressionsNo coverage regressions found. Coverage Stats
💛 - Coveralls |
56777f8 to
cb2bf4b
Compare
DanGould
left a comment
There was a problem hiding this comment.
So I for C#, we've got a lot of these and there are basically three sections that need to be covered. The conceptual stuff, the C#-specifc context, and then code examples. In my opinion, the conceptual stuff should be lifted from a commonplace that way it can be edited across all packages at once. The C#-specifics are the meat of this PR, and any code needs to be checked against the compiler/runtime so that it never goes out of date.
Conceptual stuff
Because this is meant for developers, I would do my best to match the register of BIP77 itself. Concretely, the narrative in the first sentence is quite different from how that presents.
Payjoin lets the receiver of a Bitcoin payment contribute inputs to the sender's transaction, improving privacy and enabling batching in an ordinary-looking payment.
The BIP doesn't use "payment" and I have been avoiding that word everywhere because what payjoin does is quite different than lightning, which is better designed specifically for payments. I prefer to use "transfer" to describe funds moving. And the bip doesn't describe improvements to privacy, but rather preservation of privacy by by heuristic poisoning. This is a critical frame to maintain across our developer messaging until we have further concrete guarantees we can make about "improving privacy" because the first question thereafter is naturally "by how much" and we don't have a concrete answer. Satoshi's whitepaper privacy section being addressed is around the best payjoin can do on its own.
C#-specifics
.NET requirements, supported platforms, install, is all great for package consumers. Keep it
Code
Lastly, the examples. Writing them here creates same problem that we have in old payjoindevkit.org Rust articles. If they don't compile they get stale almost immediately. So I don't really think examples belong here. I think we should have pointers to a samples/ folder perhaps or otherwise XML Doc string comments that have IntelliSense that compiles in VS Code.
Alternatively I read that https://github.com/SimonCropp/MarkdownSnippets can be used to point to the README here and actually compile / test things by using #region but haven't tried.
The move to DEVELOPMENT.md and separation in general seems correct to me. cACK
|
I'll look into incorporating this strategy |
There was a problem hiding this comment.
I would suggest using the name CONTRIBUTING.md since that is already a pattern in this repo:
There was a problem hiding this comment.
Good catch, I originally liked DEVELOPMENT.md for what this is, especially to make it distinct from policy, but it is probably better to stay consistent and then consider appling a wholesale change CONTRIBUTING -> DEVELOPMENT as a separate matter / commit / pr
There was a problem hiding this comment.
Agree with all of this.
Not relevant here but another pattern that reinforces the contributing verbiage are the contrib folders that hold the testing and linting scripts for each crate/ffi bindings.
|
Just reworked the narrative and the examples moved to a samples/ folder that compiles as part of Payjoin.Tests, with the README pointing there. Opened #1772 for the shared conceptual text across packages. |
xstoicunicornx
left a comment
There was a problem hiding this comment.
Again DEVELOPMENT.md should be CONTRIBUTING.md to align with established patterns in the repo (#1764 (comment))
Also, I am not familiar with csharp and its development flows but will do my best to review the content.
In general, is there anything in the samples that isn't already covered in the integration test? Even if there is maybe it would be better to incorporate those missing elements in the integration test instead?
Instead of creating standalone sample files that need to be maintained and have no indicator of when they are out of date it seems better to maintain an extremely well documented and straightforward integration test instead and point developers there to understand usage. Right now the integration test has no comments so would likely be hard to follow. Additionally, these piecemeal code samples are very minimal and focus on one specific aspect of the functionality but leave the developer to figure out how those pieces fit together themselves. The integration test shows how all the pieces fit together.
What do you think?
I mostly agree; I know I would probably use the integration tests or CLI. I think the samples folder should break CI when it drifts, but it won't catch behavior drift; that’s a good point. At the same time, the integration tests may not be well structured for presenting... after adding comments to the integration tests, I think your suggestion is actually better |
xstoicunicornx
left a comment
There was a problem hiding this comment.
ACK d7bc5f6
Documentation reads strictly better than before. Really like the commented integration test. Have some suggestions that I think could add clarity.
Disclaimer: I have not run all the csharp build commands myself so can't attest to all of their accuracy.
| - `ref/net10.0/Payjoin.dll` | ||
| - `runtimes/any/lib/net10.0/Payjoin.dll` | ||
| - `runtimes/{rid}/native/{native-library}` | ||
| The usage reference is the commented walkthrough in [`IntegrationTests.cs`](https://github.com/payjoin/rust-payjoin/blob/master/payjoin-ffi/csharp/IntegrationTests.cs): `TestIntegrationV2ToV2` drives a complete payjoin from both sides, executed by CI on every change so it cannot go stale, and narrates each protocol step, from opening and persisting the session through the receiver checklist to signing the proposal. |
There was a problem hiding this comment.
I would put this in its own section above Receiver a payjoin so it is immediately identifiable and not buried in text. Maybe could call the section End to end example or something similar.
| ```csharp | ||
| var uri = Payjoin.Url.Parse( | ||
| "bitcoin:12c6DSiU4Rq3P4ZxziKxzrL5LmMBrzjrJX?amount=1&pj=https://example.com?ciao"); | ||
| A sender session starts from a BIP 21 URI scanned from the receiver (`Payjoin.Uri.Parse(...).CheckPjSupported()`) and the wallet's signed PSBT, posts that original PSBT, and polls for the receiver's proposal through the same request/response flow. The sender half of the same walkthrough shows every step. |
There was a problem hiding this comment.
| A sender session starts from a BIP 21 URI scanned from the receiver (`Payjoin.Uri.Parse(...).CheckPjSupported()`) and the wallet's signed PSBT, posts that original PSBT, and polls for the receiver's proposal through the same request/response flow. The sender half of the same walkthrough shows every step. | |
| A sender session starts from a BIP 21 URI scanned from the receiver (`Payjoin.Uri.Parse(...).CheckPjSupported()`) which is used to create and sign the original PSBT which is then posted back to the receiver. Then the sender polls for the receiver's payjoin proposal through a request/response flow. Once the sender receives the payjoin proposal it can sign and broadcast it to the network. The sender half of the same walkthrough shows every step. |
Something like this that is a bit clearer might be better.
| /// usage reference the package README points at. <see cref="TestIntegrationV2ToV2"/> | ||
| /// drives a complete payjoin between two regtest wallets: the receiver opens a | ||
| /// session and produces a BIP 21 URI, the sender posts its original PSBT to an | ||
| /// untrusted directory, the receiver checks that original, contributes an input, |
There was a problem hiding this comment.
| /// untrusted directory, the receiver checks that original, contributes an input, | |
| /// untrusted directory, the receiver fetches that original PSBT, contributes an input, |
| /// drives a complete payjoin between two regtest wallets: the receiver opens a | ||
| /// session and produces a BIP 21 URI, the sender posts its original PSBT to an | ||
| /// untrusted directory, the receiver checks that original, contributes an input, | ||
| /// and posts a proposal back, and the sender signs and broadcasts the final |
There was a problem hiding this comment.
| /// and posts a proposal back, and the sender signs and broadcasts the final | |
| /// and posts a payjoin proposal back, and the sender signs and broadcasts the final |
| /// through the directory, encapsulated in OHTTP so the directory cannot link | ||
| /// client identity to session content. |
There was a problem hiding this comment.
| /// through the directory, encapsulated in OHTTP so the directory cannot link | |
| /// client identity to session content. | |
| /// through the directory, encapsulated in OHTTP so the directory cannot observe | |
| /// session content. Additionally all requests to the directory are routed through an | |
| /// OHTTP relay to prevent the directory from learning the receiver and sender identities. |
Just wanna be careful here because the directory can link the sender and receiver requests because they have the same mailbox id, and being unable to link this to the client identity is not due to OHTTP encapsulation but due to intermediary OHTTP relay.
| // own check, and Save yields the next state. A receiver that skips a check | ||
| // does not typecheck. |
There was a problem hiding this comment.
| // own check, and Save yields the next state. A receiver that skips a check | |
| // does not typecheck. | |
| // own check, and Save yields the next state. |
Maybe last sentence isn't necessary? I wasn't too sure what it meant myself.
| /// <summary> | ||
| /// Check 4: find which outputs of the original pay the receiver. These are | ||
| /// the outputs the payjoin is allowed to substitute or amend; everything | ||
| /// else belongs to the sender and stays untouched. |
There was a problem hiding this comment.
| /// else belongs to the sender and stays untouched. | |
| /// else belongs to the sender and stays untouched. This check also ensures that at least | |
| /// one output actually pays the receiver. |
| } | ||
|
|
||
| /// <summary> | ||
| /// The heart of the payjoin: the receiver contributes its own input. The |
There was a problem hiding this comment.
Love this phrasing :)
There was a problem hiding this comment.
I actually have some minor reservations about this phrasing, explained in earlier comment, but am OK with it for now here in an explanatory test comment.
There was a problem hiding this comment.
ACK 5c2db25
Updates look good. Looks like there is a merge conflict that needs to be addressed though?
Replace the README's inline code with prose and pointers to a samples/ folder that builds as part of Payjoin.Tests, so the examples are checked by the compiler on every change instead of going stale on the package page. Align the narrative with BIP 77's register while here: funds move in transfers rather than payments, and payjoin preserves privacy by poisoning the common-input-ownership heuristic rather than promising an improvement it cannot quantify.
The dart and javascript bindings document their build and test flow in a CONTRIBUTING.md, as does the repository root. Follow that pattern instead of introducing a new name.
Replace the standalone samples with narrative comments in IntegrationTests.cs and point the README there. The samples only compiled, so they proved syntax and not behavior, and each one showed a fragment while leaving the composition to the reader. The integration test runs on CI on every change and contains the complete flow, so comments on it document usage that is executed rather than merely compiled: the walkthrough narrates session bootstrap and persistence, the sender's original PSBT, each receiver check and why it exists, input contribution, the fee range, and finalization.
Give the end-to-end example its own README section, complete the sender narrative through signing and broadcast, separate the two privacy mechanisms correctly (OHTTP encapsulation hides content from the directory, the relay hides client identity; the directory can still link one session's requests through the shared mailbox), drop an unclear sentence, and note that output identification also guarantees at least one output pays the receiver.
5c2db25 to
3fd2ec4
Compare
| The NuGet package is still prepared as a preview while the C# API stabilizes. The | ||
| first release-ready package layout targets .NET 10 and ships a managed | ||
| `Payjoin.dll` plus RID-specific native `payjoin_ffi` libraries. | ||
| Payjoin lets the receiver of a Bitcoin transfer contribute inputs to the sender's transaction. The result looks like any other transaction, which preserves privacy by poisoning the common-input-ownership heuristic that chain surveillance depends on, and it lets the receiver batch its own operations into the same transaction. These bindings implement both [BIP 78](https://github.com/bitcoin/bips/blob/master/bip-0078.mediawiki) (synchronous payjoin) and [BIP 77](https://github.com/bitcoin/bips/blob/master/bip-0077.md) (asynchronous payjoin, where sender and receiver exchange the transaction through an untrusted directory and never need to be online at the same time), and ship with native libraries for every supported platform, so no Rust toolchain is required. |
There was a problem hiding this comment.
I prefer the BIP77 abstract's first sentence
Payjoin lets Bitcoin senders and receivers interact to make batched transactions.
Because the interaction is the core that lest both cooperate to choose inputs and outputs. Yes, past discussion, and most privacy consequences have been discussed in the context of input-contribution but I think that's a limited view. Just a pin to track for later.
| } | ||
|
|
||
| /// <summary> | ||
| /// The heart of the payjoin: the receiver contributes its own input. The |
There was a problem hiding this comment.
I actually have some minor reservations about this phrasing, explained in earlier comment, but am OK with it for now here in an explanatory test comment.
The NuGet page renders README.md as the package front page, but most of its content (Development, Packaging, cross-compilation) is for contributors building the bindings, not for .NET developers consuming them. This corrects it.
Disclosure: co-authored by Claude Code.
Pull Request Checklist