Inspiration

Payjoin supercharges batching save you fees and preserve privacy. Our team has been contributing to the Payjoin Dev Kit (PDK) and wanted to eat our own dogfood and apply our development kit into as many real world integrations as possible.

Some of us are Core developers of the Payjoin Dev Kit and others are relatively new contributors, being introduced to PDK development by participating in Chaincode's BOSS program.

We wanted these to be as diverse in application as possible, but all of them demonstrate the fee savings and privacy preservation in different contexts

Embedded WASM Payjoin Demo

Inspiration

When Brandon found Payjoin, he wished there was some way to understand how it works.

It's been pretty hard to write Bitcoin software for the web. These Dev Kits make it a lot easier. Now that both Bitcoin Dev Kit and PDK have web support, we figured we should make an interactive Payjoin Demo as a sort of design guide and educational tool to walk through the Payjoin process.

Before the hackathon, River was working on the infrastructure to compile PDK to Web Assembly. It lets anyone embed payjoin in browsers, compile to Node.js, or run in other web environments like React Native, since not everyone knows Rust.

What it does

With the power of vibe coding we launched a web app that educates the user on how payjoin works via a step by step walkthrough, while coordinating a real payjoin live. It is not merely an educational tool. It is the first use of PDK in JavaScript and the most complete implementation available in JavaScript.

The demo presents a sender and receiver wallet. They sync to Mutinynet using Bitcoin Dev Kit. We show examples of what sending and receiving could look like. We also give an "under the hood" look at the Payjoin process.

The UI presents the following steps for each party:

  • Receiver creates a BIP21 invoice.
  • Sender scans the BIP21.
  • Sender creates an Original PSBT, signs it, and sends it to the Payjoin Directory.
  • Receiver polls the Payjoin Directory, modifies the PSBT with their inputs, and sends it back to the Payjoin Directory
  • Sender polls the Payjoin Directory, signs the PSBT with the receiver's inputs, and broadcasts it.

And voíla, you bore witness to one the first WASM payjoins in the browser. ✨

Prepare to see many more in the future!

How we built it

Prior to the hackathon, River had a proof-of-concept of a single Mutinynet Payjoin in JavaScript via the very newly created PDK WASM bindigns. It was pure JavaScript, there was no UI.

During the hackathon, Brandon built the first UI to utilize these bindings with the idea of it being an educational tool to understand Payjoin -- something Brandon wishes he had when he was learning about Payjoin.

River and Brandon then worked together to integrate River's bindings into the demo, serving the dual goal of providing an educational UI tool to understand Payjoin while at the same time creating a fully function Payjoin implementation

Challenges we ran into

There was a lot of UI to build. We utilized Claude Sonnet 3.5 and 3.7 and built it in pure HTML and JavaScript. We used TailwindCSS for styling. The many intricate UI interactions resulted in a lot of bugs and debugging. Also, since we didn't take the time to setup the project as a TypeScript project, we ran into a lot of issues trying to access functions incorrectly and had to keep referencing the types file, slowing down development.

Additionally, we had some difficulty getting WASM to work in the browser, since it requires very specific implementation.

We also had a really cool web component element that doesn't seem to be loading in our live demo, but works locally for us. Alas! We ran out of time to debug it properly on the Netlify server we're using.

Finally, we didn't quite finish the last bit of UI, we wanted to add a "Transaction complete!" state but we only got to both the final receiver and sender loading states. It does work, however! As you can see in the video demo, open up the browser console and see the broadcastable PSBT! You can also uncomment a line in the sender code to broadcast the transaction. We wanted to set a 'dev' vs 'prod' environment variable to toggle this behavior but ran out of time.

Boltz Exchange Transaction Cut-Through

Inspiration

Greg Maxwell first mentioned Transaction Cut-Through in 2013. The idea is that multiple transaction intents can be batched into a single transaction by coordinating before broadcast. For example. If Alice wants to pay Bob, and Bob wants to pay Carol, they can coordinate to send a single transaction that spends from Alice to Carol, with Bob's change going back to himself all in one transaction. This would use less space on the chain and therefore save fees, and could be better for privacy since it reduces the public data that gets posted.

What it does

We applied this idea to Boltz Exchange Submarine Swaps. Submarine Swaps are a way to exchange Bitcoin for Lightning without any risk of the exchange taking money. The customer is always in control of their own funds.

Instead of just sending a transaction to the swaps's lockup address, the customer proposes a spend to a Payjoin endpoint. The interactive request gives the exchange a chance to contribute an input from their treasury in addition to the customer's funds. This consolidates UTXOS where otherwise Boltz would end up with many UTXOs, one from every single swap. This also breaks the common input heuristic, the last privacy problem Satoshi Nakamoto left in the original Bitcoin whitepaper, since now, inputs come from both sender and receiver.

Since each submarine swap creates a new output for Boltz, they need to consolidate them all together to make big transfers. By consolidating during payment, they can save lots and lots of fees.

We demonstrate that the transaction flow stays almost identical to the original Submarine Swap flow, batching saves fees compared to a separate spend and consolidation, and pays the invoice the customer wished to pay.

How we built it

We had to run both the boltz-backend and boltz-web-app on MacOS, which was tricky. Boltz.exchange is operated by one gentoo grey beard so he wasn't super motivated to make a developer experience that works across multiple environments.

Once we got the environment working, we had to write the Payjoin Manager to handle payjoin networking and transaction construction. We had to write the gRPC layer to have the main TypeScript process boltz backend is written in and the rust sidecar that handles payjoin. Once that was done, we could propagate our invoice to the front end. We had to wrangle the internal bitcoin-cli and lightning nodes to make invoices and wallets to run our payjoin cli in order to test payjoin with it but it worked.

Liana Inheritance Wallet

Inspiration

Liana is a wallet that supports time-based spending conditions. A simple inheritance setup might involve a primary spending key that automatically transitions to a recovery key after a specified time. Once this expiration period is reached, users must move their UTXOs to a new address to avoid the recovery key becoming active. However, this required transaction leaves an on-chain footprint, making it easier for chain analysis to cluster user addresses. Payjoin mitigates this issue by breaking the common input ownership heuristic, thereby enhancing privacy.

What it does

The Liana inheritance wallet now supports sending Payjoins via BIP-77, which is especially useful for self-transfers that consolidate UTXOs to reset their spending timers. Traditionally, users would need to gather all their UTXOs and send them to a new address—creating a transaction with a recognizable structure and witness script. This makes it easy for on-chain observers to link the receive and change addresses, undermining privacy. By contrast, BIP-77 enables collaborative transaction construction, breaking these patterns. BIP-77 and its multi-party extensions will further improve privacy and allow participants to share fee responsibilities.

Specifically, the sender will accept a BIP-77 compatible BIP-21 URI. A payjoin receiver will generate a BIP-21 URI and provide it to a Liana user. The Liana user will create a original payjoin PSBT and send it to the receiver via the BIP-77 and then long poll waiting for the reciever to contribute input and output pairs. Once the sender has received their payjoin PSBT they will sign it and broadcast it. Its important to note the sender will keep both their original transaction and the payjoin transaction incase the receiver never contributes inputs.

How we built it

Challenges we ran into

The graphical user interface is a custom rust framework called iced. It's a bit tricky to get the hang of, as it' completely different from web.

Liana generally does not support external inputs, much of the internal signing logic was refactored to allow for it.

The receiver on liana was particularly tricky to implement because we had to re-create the derivation paths manually. This required us to have a deep understanding of business logic of the wallet. And it required us to also initialize the hot wallet in some foreign environment which ended up being too un-reliable for a working demo.

Accomplishments that we're proud of

  • We got THREE working demos with completely different architectures. Our Dev kit works
  • We found out some ways to maek PDK better, like a better persistence api
  • The first payjoin in the browser via wasm
  • Liana is the first inheritance wallet to support a payjoin sender

What we learned

We learned that it's easier to use the Payjoin Dev Kit than we thought, but also that wallet internals can be very tricky to make work.

Many wallets will not support signing for foreign inputs by default.

What's next for Payjoin Integrations

WE'RE DOING MORE OF THEM !!!

We are in touch with the makers of Boltz and Liana and will be merging our proof of concepts with their production implementations.

slides: https://docs.google.com/presentation/d/15AHF3Y6vQ67XYMO5T2cGUQIxHZjTNBhr9gXQnVGPNeQ/edit?usp=sharing

Built With

  • bdk
  • boltz
  • liana
  • lightning
  • miniscriptdescriptors
  • rust
  • submarineswaps
  • wasm
Share this project:

Updates

posted an update

4 years ago, we spent the whole hackathon getting 1 payjoin app working with a hack that had limited compatibility. Now we leveraged an SDK that lets any of our 3 new apps use Payjoin with any other payjoin app, including BTCPay, Sparrow, JoinMarket, Wasabi, BlueWallet, BullBitcoin Mobile, and more soon.

Log in or sign up for Devpost to join the conversation.