LiquiDEX
LiquiDEX is a 2-step atomic swap protocol for the Liquid Network that enables trustless peer-to-peer asset exchanges. It allows users to swap Liquid Bitcoin (LBTC) and other Liquid assets without requiring a trusted third party or centralized exchange.
Overview
A LiquiDEX swap involves two parties:
- Maker: Creates a swap proposal offering to exchange one asset for another
- Taker: Accepts the proposal and completes the swap
The protocol uses an incomplete but signed transaction (unbalanced and without fees) created by the maker. The taker completes the transaction by adding inputs and outputs, balancing the amounts and adding fees. This ensures atomicity: either both parties get what they want, or the transaction cannot be broadcast.
The proposal always spend a full utxo, due there is no a way to add change address for the maker.
How It Works
-
Maker creates a proposal: The maker creates a PSET with one input (UTXO to be spent) and one output (the asset he want to receive). The transaction is signed but incomplete. The PSET is comverted in a Liquid Proposal, a structure cointaining all the relevant information for the swap.
-
Proposal validation: The taker receives the proposal and validates it using primitives available in LWK.
-
Taker completes the swap: The taker recerate the PSET and adds inputs and outputs to balance the transaction, adds fees, and signs their part.
-
Transaction broadcast: The completed transaction is broadcast to the Liquid Network, executing the atomic swap.
Key Concepts
LiquidexProposal
A LiquidexProposal represents a swap offer. It comes in two states:
- Unvalidated: The proposal has been created but not yet verified
- Validated: The proposal has been verified and is ready to be taken
Creating a Swap Proposal (Maker)
The maker creates a swap proposal by building a transaction with liquidex_make(), signing it, and converting it to a proposal:
// LiquiDEX make
let addr = wallet_maker.address_result(None).address().clone();
let mut pset = wallet_maker
.tx_builder()
.liquidex_make(utxo_send, &addr, sats_recv, asset_recv)
.unwrap()
.finish()
.unwrap();
let details = wallet_maker.wollet.get_details(&pset).unwrap();
wallet_maker.sign(signer_maker, &mut pset);
let proposal = LiquidexProposal::from_pset(&pset).unwrap();
# (maker) Create a liquidex proposal (asking for the issued asset in exchange for the policy asset)
builder = network.tx_builder()
utxo = maker.utxos()[0].outpoint()
builder.liquidex_make(utxo, maker.address(None).address(), issued_asset_units, asset)
pset = builder.finish(maker)
signed_pset = signer.sign(pset)
# (maker) Create the proposal and convert it to string to pass it to the taker
proposal = UnvalidatedLiquidexProposal.from_pset(signed_pset)
proposal_str = str(proposal)
Validating a Proposal (Taker)
Before accepting a proposal, the taker must validate it by fetching the previous transaction and verifying the proposal:
let txid = proposal.needed_tx().unwrap();
let tx = wallet_maker.wollet.transaction(&txid).unwrap().unwrap().tx;
let proposal = proposal.validate(tx).unwrap();
# (taker) Parse the proposal from string and validate it
proposal_from_str = UnvalidatedLiquidexProposal(proposal_str)
txid = proposal_from_str.needed_tx()
previous_tx = client.get_tx(txid)
validated_proposal = proposal_from_str.validate(previous_tx)
Taking a Proposal (Taker)
Once validated, the taker can accept the proposal by using liquidex_take() to complete the transaction:
// LiquiDEX take
let mut pset = wallet_taker
.tx_builder()
.liquidex_take(vec![proposal])
.unwrap()
.finish()
.unwrap();
wallet_taker.sign(signer_taker, &mut pset);
let _txid = wallet_taker.send(&mut pset);
# (taker) Accept the proposal
builder2 = network.tx_builder()
builder2.liquidex_take([validated_proposal])
pset2 = builder2.finish(taker)
signed_pset2 = signer2.sign(pset2)
# (taker) Finalize and broadcast the transaction
finalized_pset = taker.finalize(signed_pset2)
tx = finalized_pset.extract_tx()
txid = client.broadcast(tx)
Additional Resources
Previous: Ledger