
How to Add PayJoin Privacy to Your Bitcoin Wallet Using PayJoin Dev Kit
A step-by-step technical guide for wallet developers to implement PayJoin V2 transactions using PDK, breaking chain analysis heuristics.
Every Bitcoin transaction you make leaves a trail. Chain analysis firms exploit a simple assumption: all inputs in a transaction belong to the same person. This "common input ownership heuristic" has become the backbone of blockchain surveillance. PayJoin breaks it.
By combining inputs from both sender and receiver into a single transaction, PayJoin makes collaborative payments indistinguishable from ordinary ones. For wallet developers looking to give users meaningful privacy, implementing PayJoin V2 with the PayJoin Dev Kit is now straightforward enough to complete in about 30 minutes.
What PayJoin Actually Does
In a standard Bitcoin transaction, if you see three inputs, the reasonable assumption is that one entity controls all three. Chain analysis tools use this pattern to cluster addresses and trace funds across the network.
PayJoin (also called P2EP, or Pay-to-Endpoint) disrupts this pattern at its core. When a sender pays a receiver, both parties contribute inputs to the transaction. The result looks like any normal payment on-chain, but the ownership assumption no longer holds.
The privacy benefit is mutual. The sender's transaction history becomes harder to trace, and the receiver's UTXO set gets consolidated without revealing which inputs belong to whom. No third-party trust required.
Why PayJoin V2 Matters for Wallet Integration
The original PayJoin protocol (V1) had a significant limitation: both parties needed to be online simultaneously. For mobile wallets or intermittent connections, this was impractical.
PayJoin V2, specified in BIP77, solves this through asynchronous coordination. The protocol uses Oblivious HTTP (OHTTP) to relay encrypted messages through a directory server. The directory only sees an 8-kilobyte encrypted blob; it never learns transaction details or IP addresses.
This architectural change makes PayJoin viable for the kinds of wallets people actually use.
Prerequisites for Implementation
Before diving into code, you'll need:
- Rust programming environment with Cargo
- A running Bitcoin Core node (use Signet for testing)
- OHTTP key configuration from a Payjoin Directory
- The `payjoin` crate from PayJoin Dev Kit
The PayJoin Dev Kit is a full, standalone implementation maintained by the PayJoin Foundation, which received 501(c)(3) non-profit status in July 2025. The project has explicit goals to accelerate adoption through 2026, with funding support from OpenSats.
Step-by-Step Implementation for Receiving PayJoin V2
The receiving side is typically more complex, so let's walk through it.
Step 1: Fetch OHTTP Keys
First, your wallet needs to obtain OHTTP keys from the Payjoin Directory via an Oblivious HTTP Relay. This establishes the encrypted communication channel.
```rust
use payjoin::receive::v2::Receiver;
// Fetch OHTTP keys from directory via relay
let ohttp_keys = fetch_ohttp_keys_from_directory(&relay_url, &directory_url).await?;
```
The relay acts as a privacy layer between your wallet and the directory, ensuring the directory never learns your IP address.
Step 2: Initialize a Receiver Session
With OHTTP keys in hand, initialize a receiver session that will listen for incoming PayJoin proposals.
```rust
let receiver = Receiver::new(
address,
directory_url,
ohttp_keys,
expire_after,
)?;
```
The session handles the asynchronous coordination, meaning your wallet can go offline and return later to complete the transaction.
Step 3: Generate a PayJoin URI
PayJoin URIs extend standard BIP21 URIs with the endpoint information senders need. Generate one and present it to your user (typically as a QR code).
```rust
let pj_uri = receiver.pj_uri();
// Display as QR code or shareable string
```
Senders with PayJoin-compatible wallets will automatically recognize the URI and initiate the collaborative protocol.
Step 4: Validate Incoming Proposals
When a sender creates a proposal, your wallet receives it through the encrypted relay channel. The PayJoin Dev Kit provides check methods to validate proposals before proceeding.
```rust
let proposal = receiver.process_response(response)?;
// Validate the proposal meets your requirements
let checked = proposal
.check_broadcast_suitability(None, |tx| check_broadcast(&tx))?
.check_inputs_not_owned(|script| is_owned(script))?
.check_no_mixed_input_scripts()?
.check_no_inputs_seen_before(|outpoint| is_known(outpoint))?;
```
These checks prevent various attacks and ensure the proposal is legitimate.
Step 5: Contribute Your Inputs
This is where the privacy magic happens. Your wallet adds its own inputs to the transaction, breaking the common input ownership heuristic.
```rust
let provisional = checked.identify_receiver_outputs(|script| is_mine(script))?;
// Select UTXOs to contribute
let selected_utxo = select_utxo_for_payjoin();
let payjoin = provisional.contribute_witness_input(selected_utxo, outpoint)?;
```
The selection of which UTXOs to contribute is a wallet design decision. Contributing larger UTXOs provides more privacy benefit but requires more capital.
Step 6: Finalize and Sign
Complete the transaction by finalizing the proposal and signing your contributed inputs.
```rust
let payjoin_psbt = payjoin.finalize_proposal(
|psbt| wallet.sign(psbt),
Some(min_feerate),
)?;
// Send back through the relay channel
receiver.respond(payjoin_psbt)?;
```
The sender receives the modified PSBT, verifies it, adds their signature, and broadcasts the final transaction.
Implementation Considerations
A few practical notes from the protocol documentation and developer discussions:
Fee handling: The receiver typically doesn't increase the fee, but the protocol allows for fee contributions. Decide on a policy that makes sense for your wallet's use case.
UTXO selection: Random selection is simple but not optimal. Consider implementing strategies that maximize privacy benefit, such as preferring UTXOs of similar value to the payment amount.
Error handling: The async nature of V2 means you need robust handling for expired sessions, network failures, and malformed proposals. The PDK provides typed errors to help with this.
Testing: Use Bitcoin Signet rather than mainnet during development. The PayJoin Foundation maintains test infrastructure for developers.
The Adoption Challenge
PayJoin's effectiveness depends on network effects. A PayJoin transaction requires both sender and receiver to support the protocol. With limited wallet adoption, users can't always take advantage of the privacy benefits.
BTCPay Server has supported PayJoin for merchant payments, and the PayJoin Foundation's push through 2026 aims to expand wallet integration significantly. Dan Gould, lead developer of PayJoin Dev Kit, has stated the project's goal is to "accelerate Payjoin adoption in 2026."
The more wallets that implement PayJoin, the more useful it becomes for everyone. This creates a coordination problem, but also an opportunity: early implementers position their wallets as privacy-forward options in an increasingly surveillance-aware market.
Moving Forward
PayJoin represents a pragmatic approach to Bitcoin privacy. It doesn't require protocol changes, doesn't break Bitcoin's transparency guarantees for auditing, and provides mutual benefit to both transaction parties.
For wallet developers, the PayJoin Dev Kit removes most implementation complexity. The code is production-ready, the protocol is standardized in BIP77, and the supporting infrastructure (relays, directories) exists.
The technical barrier is low. The remaining challenge is coordination: getting enough wallets to implement PayJoin that users can actually use it. If you're building a Bitcoin wallet and privacy matters to your users, this is worth the 30 minutes to prototype.