You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
The current lock-release design for BTC -> Solana swaps requires the LP to lock up a capital on the Solana side in a Solana transaction prior to a client sending in the BTC, in the protocol this cost is shifted on the user (where the user needs to cover the Solana transaction fee). In case of BTC -> Solana the user holds a free option open for 6 hours, therefore the user also needs to pay up a non-refundable deposit acting as an options premium for the LP - in case the user doesn't go through with the swap. All things considered, this means a user needs to have some small amount of SOL tokens before being able to initiate a BTC -> Solana swap. This was somewhat mitigated with a trusted "swap for gas" feature, however that only works with bitcoin lightning (due to the instant settlement) and would be cumbersome to use with on-chain (as user would have to wait for the on-chain tx to confirm first, and only then could he initate a proper trustless swap).
Here we outline a novel way to handle BTC -> Solana swaps which don't require initial locking transaction on the Solana side, allowing users to initiate swaps with 0 SOL balance. We iteratively go over different approaches until we end up with a secure approach of using deposit only vault with UTXO chaining.
Simple naive approach
A simple (but insecure) approach would be to eliminate the lock transaction - the funds of the LP are in the smart contract's vault anyway. Let's say an LP has 100 SOL in the LP vault - the user would just send certain amount of BTC to the pre-determined LP address on the bitcoin side, specifying his Solana address in an OP_RETURN of the transaction, watchtowers on Solana would then (upon confirmation of the TX), claim the corresponding amount of SOL (calculated from e.g. 3rd party oracle price) from the LP vault, with a transaction proof. In this approach it is important for the smart contract to save already claimed transaction IDs, so same transaction cannot be used to claim the SOL multiple times.
Problem in this system is that the LP can withdraw the 100 SOL from the LP vault at any time, and when user's bitcoin transaction confirms, there isn't any SOL left in the LP vault to pay out to the user on the Solana side.
Deposit only vault
To iterate on the previous approach we can let the LP vault be deposit only (e.g. the LP cannot withdraw funds from it, or can withdraw but with a long enough delay). This way the LP doesn't have a way anymore to simply "rug" the user.
However this approach is also not secure, imagine now another user (or bunch of users) also want to swap from BTC to SOL, so many that they would collectively depelete the 100 SOL available liqudity. This means only the users who can get their transactions confirmed first (pay the highest fee or bribe the miners) on the BTC side would be able to get the SOL, others would be "rugged" and left with no money. Astute reader can also see how an evil LP itself can act as "another user" and send enough BTC to the address (basically to self) with high enough fee to get into right the next block, depelete all its available liqudity and get free BTC by "rugging" the users.
Deposit only vault with utxo chaining
The issue with the previous approach is that we cannot be sure on the ordering of the transactions as they are sent on the bitcoin side (i.e. anyone can pay a higher btc tx fee and send any amount of BTC to the address before the client does).
We can make the ordering deterministic by introducing a dust UTXO chaining & adding UTXO chaining input/output to every swap transaction.
An LP, upon depositing 100 SOL to the vault, would specify a vault UTXO (a dust UTXO - 330 sats on the bitcoin blockchain) controlled by him. The UTXO identifier (txId:vout) would be saved in the vault's state, and would be required to be used by a swap transaction on the bitcoin's side. The swap transaction would also have one additional dust UTXO controlled by the LP, which would replace the prior vault's UTXO, such that the next swap transaction needs to use that UTXO, this creates a "UTXO chain", making swap transactions deterministically ordered.
Moreover, since now both LP's (because of the UTXO chaning input) and user's signatures are required to construct a transaction, we can include the swap's SOL output amount in the OP_RETURN data part of the transaction, making the output amount pre-determined!
This allows any potential swapper to go back the UTXO chain all the way to the UTXO specified in the vault's state on Solana, sum up the output amounts, deduct them from the current vault's balance and see if there is enough SOL left for the user to claim, without getting "rugged".
Example UTXO chained transactions:
1st transaction (TX1) 2nd transaction (TX2)
Inputs: Outputs: Inputs: Outputs:
>-- Vault UTXO 0 ---+---+--- Vault UTXO 1 ---> - - - >-- Vault UTXO 1 ---+---+--- Vault UTXO 2 --->
>-- User A UTXO 0 --+ +--- LP address -----> >-- User B UTXO 0 --+ +--- LP address ----->
... | +--- OP_RETURN data -> ... | +--- OP_RETURN data ->
>-- User A UTXO N --+ +--- User A change --> >-- User B UTXO N --+ +--- User B change -->
Here the vault UTXO is always controlled by LP & User A swapped before User B. We can also see that if TX1 were to change (e.g. the amount sent changes), the Vault UTXO 1 txId reference would change as well, invalidating the TX2 and User B's swap.
Step-by-step process
LP creates a vault on the Solana side, specifying the vault's initial UTXO
User sends an RFQ to the LP for BTC -> SOL swap
LP responds with the output amount (in SOL), its bitcoin address & latest UTXO in the vault's UTXO chain
User verifies that the UTXO returned is a valid one, has to be unspent and is either:
the same one as specified in the current solana vault's state
traces back to the one specified in the current solana vault's state, in this case additional checks are performed:
summing up all the transaction's SOL outputs that will be paid before this swap leaves enough SOL in the vault for the current swap -> vault's balance - sum(previous swap SOL outputs) > current swap SOL output
User checks whether he is happy with the swap pricing
User constructs a bitcoin transaction & signs its inputs (User's UTXO 0-N)
LP checks the PSBT is correctly constructed: contains valid Vault UTXO on input, creates a new valid Vault UTXO, pays out to his BTC address & specifies correct output SOL amount in OP_RETURN data
LP signs the Vault UTXO input of the transaction and broadcasts the transaction
Transaction confirms & is processed by swap watchtowers, which claim the swap on behalf of the user & user gets his SOL
Vault withdrawals
Vault withdrawals are also possible under this system, with the only pre-condition being that the vault processed all the submitted swap transactions. We can prove this by burning the Vault UTXO with OP_RETURN, then processing all the swaps until the vault UTXO was burned.
1st swap transaction (TX1) 2nd swap transaction (TX2) burn transaction (TXB)
Inputs: Outputs: Inputs: Outputs: Inputs: Outputs:
>-- Vault UTXO 0 ---+---+--- Vault UTXO 1 ---> - - - >-- Vault UTXO 1 ---+---+--- Vault UTXO 2 ---> - - - >---- Vault UTXO 2 ---+---+--- OP_RETURN --->
>-- User A UTXO 0 --+ +--- LP address -----> >-- User B UTXO 0 --+ +--- LP address ----->
... | +--- OP_RETURN data -> ... | +--- OP_RETURN data ->
>-- User A UTXO N --+ +--- User A change --> >-- User B UTXO N --+ +--- User B change -->
TX1 & TX2 would therefore have to be processed before the LP can withdraw funds from the vault
Drawbacks
Larger bitcoin transactions
As all swap transactions also need to include the UTXO chain input & output + OP_RETURN data, the minimum size of the transaction (using p2tr) is 305.5vB, this is 2x bigger than regular 1-in 2-out spends (again using p2tr), which are just 154vB.
LPs can cancel unconfirmed transactions
LPs can double-spend the vault utxo on the input of the swap transaction, invalidating the bitcoin transaction, in this case no money is lost (not even on TX fee) but this would result in a not so great UX for the user. A user would be presented with "Waiting for confirmations" screen, and then some minutes in a "Failed" screen would appear and he would have to go through a swap UI again.
This can be mitigated by LPs posting a slashable bond on the Solana side, such that when 2 signed txns using the same vault UTXO are presented (double-spend by LP) the bond can be slashed.
Prior users can cancel subsequent unconfirmed transactions
If a user A swaps through an LP (TX1) and its transaction is still unconfirmed & now user B also swaps through the same LP (TX2 - using the vault UTXO of the user A's swap for chaining), user A can double-spend it's inputs to the transaction, invalidating the transaction (and also the newly created vault UTXO)
1st transaction (TX1) 2nd transaction (TX2)
Inputs: Outputs: Inputs: Outputs:
>-- Vault UTXO 0 ---+---+--- Vault UTXO 1 ---> - - - >-- Vault UTXO 1 ---+---+--- Vault UTXO 2 --->
>-- User A UTXO 0 --+ +--- LP address -----> >-- User B UTXO 0 --+ +--- LP address ----->
+--- OP_RETURN data -> +--- OP_RETURN data ->
+--- User A change --> +--- User B change -->
... after User A double-spends UTXO 0
1st transaction (TX1) - INVALID 2nd transaction (TX2) - INVALID
Inputs: Outputs: Inputs: Outputs:
>-- Vault UTXO 0 ---+---+--- Vault UTXO 1 ---> - - - >- (doesn't exist) -+---+--- Vault UTXO 2 --->
>- (double-spent) --+ +--- LP address -----> >-- User B UTXO 0 --+ +--- LP address ----->
+--- OP_RETURN data -> +--- OP_RETURN data ->
+--- User A change --> +--- User B change -->
Therefore to be perfectly safe the vault UTXO should first be confirmed (at least 1 confirmation) and only then used by the other user. This however limits the number of swaps that a single LP can process (there are onlt 144 blocks in a day), so one can imagine a single LP managing multiple vaults, allowing swaps to happen in parallel, but this also decreases capital efficiency.
reacted with thumbs up emoji reacted with thumbs down emoji reacted with laugh emoji reacted with hooray emoji reacted with confused emoji reacted with heart emoji reacted with rocket emoji reacted with eyes emoji
-
The current lock-release design for BTC -> Solana swaps requires the LP to lock up a capital on the Solana side in a Solana transaction prior to a client sending in the BTC, in the protocol this cost is shifted on the user (where the user needs to cover the Solana transaction fee). In case of BTC -> Solana the user holds a free option open for 6 hours, therefore the user also needs to pay up a non-refundable deposit acting as an options premium for the LP - in case the user doesn't go through with the swap. All things considered, this means a user needs to have some small amount of SOL tokens before being able to initiate a BTC -> Solana swap. This was somewhat mitigated with a trusted "swap for gas" feature, however that only works with bitcoin lightning (due to the instant settlement) and would be cumbersome to use with on-chain (as user would have to wait for the on-chain tx to confirm first, and only then could he initate a proper trustless swap).
Here we outline a novel way to handle BTC -> Solana swaps which don't require initial locking transaction on the Solana side, allowing users to initiate swaps with 0 SOL balance. We iteratively go over different approaches until we end up with a secure approach of using deposit only vault with UTXO chaining.
Simple naive approach
A simple (but insecure) approach would be to eliminate the lock transaction - the funds of the LP are in the smart contract's vault anyway. Let's say an LP has 100 SOL in the LP vault - the user would just send certain amount of BTC to the pre-determined LP address on the bitcoin side, specifying his Solana address in an OP_RETURN of the transaction, watchtowers on Solana would then (upon confirmation of the TX), claim the corresponding amount of SOL (calculated from e.g. 3rd party oracle price) from the LP vault, with a transaction proof. In this approach it is important for the smart contract to save already claimed transaction IDs, so same transaction cannot be used to claim the SOL multiple times.
Problem in this system is that the LP can withdraw the 100 SOL from the LP vault at any time, and when user's bitcoin transaction confirms, there isn't any SOL left in the LP vault to pay out to the user on the Solana side.
Deposit only vault
To iterate on the previous approach we can let the LP vault be deposit only (e.g. the LP cannot withdraw funds from it, or can withdraw but with a long enough delay). This way the LP doesn't have a way anymore to simply "rug" the user.
However this approach is also not secure, imagine now another user (or bunch of users) also want to swap from BTC to SOL, so many that they would collectively depelete the 100 SOL available liqudity. This means only the users who can get their transactions confirmed first (pay the highest fee or bribe the miners) on the BTC side would be able to get the SOL, others would be "rugged" and left with no money. Astute reader can also see how an evil LP itself can act as "another user" and send enough BTC to the address (basically to self) with high enough fee to get into right the next block, depelete all its available liqudity and get free BTC by "rugging" the users.
Deposit only vault with utxo chaining
The issue with the previous approach is that we cannot be sure on the ordering of the transactions as they are sent on the bitcoin side (i.e. anyone can pay a higher btc tx fee and send any amount of BTC to the address before the client does).
We can make the ordering deterministic by introducing a dust UTXO chaining & adding UTXO chaining input/output to every swap transaction.
An LP, upon depositing 100 SOL to the vault, would specify a vault UTXO (a dust UTXO - 330 sats on the bitcoin blockchain) controlled by him. The UTXO identifier (txId:vout) would be saved in the vault's state, and would be required to be used by a swap transaction on the bitcoin's side. The swap transaction would also have one additional dust UTXO controlled by the LP, which would replace the prior vault's UTXO, such that the next swap transaction needs to use that UTXO, this creates a "UTXO chain", making swap transactions deterministically ordered.
Moreover, since now both LP's (because of the UTXO chaning input) and user's signatures are required to construct a transaction, we can include the swap's SOL output amount in the OP_RETURN data part of the transaction, making the output amount pre-determined!
This allows any potential swapper to go back the UTXO chain all the way to the UTXO specified in the vault's state on Solana, sum up the output amounts, deduct them from the current vault's balance and see if there is enough SOL left for the user to claim, without getting "rugged".
Example UTXO chained transactions:
Here the vault UTXO is always controlled by LP & User A swapped before User B. We can also see that if TX1 were to change (e.g. the amount sent changes), the Vault UTXO 1 txId reference would change as well, invalidating the TX2 and User B's swap.
Step-by-step process
vault's balance - sum(previous swap SOL outputs) > current swap SOL output
Vault withdrawals
Vault withdrawals are also possible under this system, with the only pre-condition being that the vault processed all the submitted swap transactions. We can prove this by burning the Vault UTXO with OP_RETURN, then processing all the swaps until the vault UTXO was burned.
Simple vault burn transaction:
Putting it into our example chained transactions:
TX1 & TX2 would therefore have to be processed before the LP can withdraw funds from the vault
Drawbacks
Larger bitcoin transactions
As all swap transactions also need to include the UTXO chain input & output + OP_RETURN data, the minimum size of the transaction (using p2tr) is 305.5vB, this is 2x bigger than regular 1-in 2-out spends (again using p2tr), which are just 154vB.
LPs can cancel unconfirmed transactions
LPs can double-spend the vault utxo on the input of the swap transaction, invalidating the bitcoin transaction, in this case no money is lost (not even on TX fee) but this would result in a not so great UX for the user. A user would be presented with "Waiting for confirmations" screen, and then some minutes in a "Failed" screen would appear and he would have to go through a swap UI again.
This can be mitigated by LPs posting a slashable bond on the Solana side, such that when 2 signed txns using the same vault UTXO are presented (double-spend by LP) the bond can be slashed.
Prior users can cancel subsequent unconfirmed transactions
If a user A swaps through an LP (TX1) and its transaction is still unconfirmed & now user B also swaps through the same LP (TX2 - using the vault UTXO of the user A's swap for chaining), user A can double-spend it's inputs to the transaction, invalidating the transaction (and also the newly created vault UTXO)
... after User A double-spends UTXO 0
Therefore to be perfectly safe the vault UTXO should first be confirmed (at least 1 confirmation) and only then used by the other user. This however limits the number of swaps that a single LP can process (there are onlt 144 blocks in a day), so one can imagine a single LP managing multiple vaults, allowing swaps to happen in parallel, but this also decreases capital efficiency.
Beta Was this translation helpful? Give feedback.
All reactions