The current swap-in flow for Phoenix uses the following steps:
- Derive a BIP 32 p2wpkh address from the user's seed
- Send funds to that address from an external on-chain wallet
- Wait for confirmations
- Fund a channel with ACINQ with utxos from that address
- Wait for confirmations
We have to wait for confirmations in step 5 because the user may double-spend the funding transaction, since they have full control of the address they're spending from. This creates some UX issues: while a swap-in is unconfirmed, it's hard for the user to understand the state of its channel and how much they can spend/receive. They cannot use the swapped funds yet, and other on-chain operations (e.g. splice-out, pay-to-splice) are stacked on top of the swap-in and the corresponding balance cannot be used yet (even though the user has released the preimage for the corresponding HTLCs). But it makes recovery very simple: the user can simply import its seed in Electrum, and will directly have access to all of its on-chain funds, including incomplete swap-ins.
We can avoid waiting for confirmations in step 5 by changing the swap-in address. Instead of using a p2wpkh address, we use a p2wsh address with the following two script branches:
- multisig 2-of-2 (ACINQ + Phoenix)
- relative delay + signature from Phoenix
Once funds have been confirmed on that p2wsh address, Phoenix and ACINQ use the multisig 2-of-2 branch to fund or splice into a channel. This can then use 0-conf, as long as we are not too close to the relative delay. That is the core idea of the swap-in potentiam proposal.
What's really nice is that from a UX point-of-view:
- we can track unconfirmed swap-in funds separately from our channel funds
- once they are confirmed, we can immediately "move them" to our channel balance
- while a swap-in is unconfirmed, the channel can still be updated with 0-conf operations (e.g. splice-out, pay-to-splice)
- every change to the channel is 0-conf, which makes it clear to the user how much they can send/receive at any time
There are drawbacks though:
- it creates bigger transactions, because spending a 2-of-2 is more expensive than spending a p2wpkh, so it costs more on-chain fees (this can be addressed in the future by using taproot and musig2)
- recovery is harder: if the user initiates a swap-in, uninstall Phoenix and imports its seed into Electrum, they won't see the swap-in funds (we'd need support for script descriptors which is still WIP)
- we'll need a custom tool to let them recover their funds in that case (or build it into Phoenix directly)
- it's simple to implement, but it isn't a great UX
- but maybe this is a rare enough edge case that we can handle through support?
- if the user initiates a swap-in, but its liquidity policy blocks the channel open/splice, the funds are hard to recover:
- if we get close to the relative delay, we cannot safely open the channel anymore and need to send the funds back to the user
- if the user wants to "cancel" the swap-in, they'll still have to pay fees to send the money back to their standard p2wpkh wallet
- ACINQ needs to make sure the 0-conf transactions confirm before reaching the relative delay (so we need to use a large relative delay)
The relative delay is only really necessary if ACINQ disappear, so we can probably use 6 months. This gives us enough time to hope that transactions will confirm, even if we set a relatively low feerate (that's part of our risk model). ACINQ may have to CPFP chains of unconfirmed transactions when they get too large: but that's true in both models, because we're otherwise limited by bitcoin relay policy (no more than 25 unconfirmed transactions)