The Getting Started instructions can be found here.
- Abstract
- Motivation
- Preliminary Discussion
- Specification
- Features Discussion
- Benchmarks and Fee Estimations
- FAQ
- Conclusion
cardano-swaps
is a proof-of-concept implementation of an organically scalable DEX protocol for the Cardano Settlement Layer (CSL). It solves many of the pitfalls of current DEX implementations by empowering users to deploy their own (and interact with each others') script addresses. By doing so, users always maintain spending and delegation control of their assets, and can elect if/when to upgrade their addresses to new contract standards.
Many DEXes on Cardano are currently implemented in ways that lock users' assets into a tightly fixed, and/or centrally maintained, set(s) of script addresses. Such design patterns are reminiscent of the EVM's accounts-based programming paradigm, and inherit many of the same downsides; scalability bottlenecks and asset/stake centralization. DEXes that hope to achieve massive scale on the CSL must adhere to a radically different approach that takes full advantage of the concurrency and parallelism offered by the eUTxO model. cardano-swaps
is a first attempt at such an approach.
To appreciate the necessity of new DEX standards, it first important to understand the deficiencies of the current status-quo:
One consequence of centralized script addresses is the necessity for liquidity pools and LP providers as discrete entities. LPs are a common feature of many DEXes, coming with undesirable properties and corresponding sets of workaround "solutions". However, these workarounds have issues themselves, as explored here:
Undesirable Property | Workaround "Solution" | Issues |
---|---|---|
Impermanent Loss | Yield Farming & Concentrated Liquidity | Medium - Long term unsustainability |
Incomplete or No Delegation Control |
|
|
Scaling Bottlenecks | Batchers, Execution/Routing Engines, and/or other middlemen | Middlemen can take advantage of their position between users and the protocol. Even if MEV is mitigated, more users --> more execution demand --> centralization of middlemen |
Of course, this is not an exhaustive list, and even if some workarounds can be somewhat effective, the underlying design principles are suboptimal.
The more decoupled delegation control is from the owner, the more distorted Ouroboros' game theory becomes. It is difficult to predict the extent of this distortion, so minimizing it is of critical importance. Additionally, current implementations of order-book style DEXes (which don't use LPs) still suffer from the scalability challenges of centralized/batched script addresses. No matter how performant/decentralized a system of batchers is, their resources do not scale in proportion to the number of users.
All of this is to say that, much like Bittorrent and the CSL-CCL stack, the best p2p protocols are ones that scale in proportion to the number of users. DEXes are no different. With this in mind, here is the key takeaway: even if users control their own keys, for a DEX to achieve true scale its users must also control their own addresses.
Cardano-Swaps achieves batcher/router-free scalability with delegation control via a novel combination of user-controlled script addresses and Beacon Tokens (both are expanded upon below).
First proposed by Axo in their original whitepaper, programmable swaps are a more promising design choice than liquidity pools. Swaps are simply UTxOs that may be consumed if and only if the resulting TX outputs satisfy the input script's logic. Swaps can be fragmented across many user-controlled addresses, so delegation control is maintained. Users then execute swaps from each other's addresses. Since each swap is atomic and explicitly defined, in aggregate they are the optimal expression of (intra)market sentiment. This design pattern scales naturally, since there must be at least as many swap addresses as there are users.
The challenge now becomes one of indexing: how do users differentiate each others' swap addresses from all other addresses on Cardano, without relying on a specialized indexer/router? This is where Beacon Tokens come into play.
Beacon Tokens are a (WIP) native token standard that "tag" on-chain data in a way that is efficiently queryable by off-chain APIs. They enable cardano-swaps users to designate their script addresses as "swappable", such that they stand out in sea of other addresses. DDOS/bloat prevention is achieved by carefully marrying Beacons' minting policies with scripts' spending policies. This is expanded upon in the Specification section below.
✅ The novel use of Beacon Tokens for "tagging" on-chain data can be generalized for many dApps, not just DEXes. More on this in the Beacon Token CIP.
Putting this all together, we finally have:
Cardano-Swaps takes inspiration from Axo's programmable swaps design, adds delegation control as a foundational feature, and, through the use of Beacon Tokens, removes the need for specialized indexers. The only remaining bottleneck is the querying capacity of existing off-chain APIs, such as Blockfrost or Koios. (For users with powerful enough hardware, even this is not a limitation, as they can run their own API database).
Here are some of the key features of Cardano-Swaps:
- Full Custody - users always maintain full spending and delegation control over their assets.
- Natural Concurrency - throughput scales with the number of users. No specialized batchers/indexers required.
- Composable Swaps - many swaps can be fulfilled in a single transaction by "chaining" swap logic
- Emergent Liquidity - arbitragers are incentivized to spread liquidity to all trading pairs
- Zero Slippage - minimum swap prices are explicitly defined
- No Superfluous "DEX" Tokens - ADA is all you need to pay for TX fees.
- Democratic Upgradability - users choose if/when to use new contracts.
- Frontend Agnosticism - relatively straightforward integration with existing frontends (i.e. wallets)
Some of these features are explained further in the Discussion section below
Cardano addresses are made up of both a payment credential and a staking credential. As long as the staking credential is unique to the user, delegation control over the address is maintained. Cardano-swaps leverages this duality by giving users addresses that are composed of the same spending scripts (per swap pair) and unique staking credentials. The spending credential is implemented in a way that gives the staking credential authority over all owner related actions, besides the actual swap execution.
❗ To force the use of a staking credential, it is not possible to mint a beacon token to an address without a staking credential.
Beacon Tokens are used to tag cardano-swaps
addresses so they are readily queryable via an off-chain API, such as Koios or Blockfrost. It is relatively straightforward to find all addresses that contain a specific native token. Here are some examples:
Task | Koios Api | Blockfrost Api |
---|---|---|
Addresses with a beacon | api | api |
UTxOs at the address | api | api |
The UTxO query also returns which UTxOs contain reference scripts, allowing users to execute their swaps.
Technically, all native tokens can be used as beacons like this but this feature is usually not the intended one. The name Beacon Token refers to any native token whose only purpose is to act as a tag/beacon.
Every trading pair gets its own spending script. This is accomplished with the help of an extra parameter. Here is the data type of that extra parameter:
data SwapConfig = SwapConfig
{ swapOffer :: (CurrencySymbol,TokenName)
, swapAsk :: (CurrencySymbol,TokenName)
}
Each combination of swapOffer
and swapAsk
results in a different spending script. All spending scripts are identical except for this extra parameter. The hash of the resulting spending script is then used as an extra parameter to the beacon policy. All beacon policies are identical except for this extra parameter. As a result, every SwapConfig
will also have its own unique beacon policy.
Since the beacon policy-id itself carries all the information needed to determine which trading pair is being used, every beacon uses the empty token name - this is enforced by the minting policy.
Minting beacons for cardano-swaps
is a tightly controlled process. In order to mint beacons, all of the following must be true:
- Only one beacon is minted per Tx.
- The minted beacon uses an empty token name.
- The beacon is minted to an address protected by the
cardano-swaps
spending script for a particular trading pair. - The beacon is minted to an address with a staking credential (either a pubkey or a script).
- The beacon is stored in the UTxO containing the reference script of the spending script for that trading pair.
- The datum of the output containing the beacon must have the proper beacon symbol.
Once the beacon is minted to the swap address, the spending script does not allow consuming the beacon's UTxO unless the beacon is being burned. This is done to prevent beacons from being sent to unrelated addresses.
Since minting and spending beacons are so heavily controlled, there is no reason to regulate burning. Burning is always allowed as long as the burn redeemer is only used to burn beacons.
Below is an example response from querying the beacon tokens:
[
{
"assets": [
{
"asset": "lovelace",
"quantity": 150000000
}
],
"price_denominator": 1,
"price_numerator": 1,
"swap_address": "addr_test1xqc8dluz63hw3z5jf38nj8vc8hr6w3zffqype75rwzhfqtmgzy4wvf0pv6q4z499a70zm6sadjzwc0w6xx622s2fx30qsq8ppj",
"swap_ref_script_id": "325f5c8028f867c3dfdcacf750cab0fb43b2ad82d8d606c5b94142a5eb4fd58f#0",
"utxo_id": "325f5c8028f867c3dfdcacf750cab0fb43b2ad82d8d606c5b94142a5eb4fd58f#1"
}
]
Only one UTxO was found, containing only Lovelace (ADA). Notice how this UTxO's required reference script ID was also returned. This response has everything a user needs to swap with the address.
While these beacons are used to "tag" all necessary information for engaging in swaps (reference script UTxOs and available swap UTxOs), they can be used for tagging ANY on-chain data w.r.t. to:
- The address containing the Beacon
- The UTxO containing the Beacon
- The information for any transaction(s) the Beacon has ever been in
For example, it is possible to query the metadata of all transactions the Beacon was ever a part of. This can be used to create an easily queryable & trustless "metadata history" trail. Here are some examples:
Blockfrost | Koios | |
---|---|---|
TX history | api | api |
TX metadata | api | api |
Beacon Tokens make all of this information readily queryable; no configuration of the tokens is necessary. This can be used in a wide variety of dApps - the only requirement is that the beacon token is unique for each kind of datatype.
For users to see each others' asking prices, all datums for the DEX must be inline datums. cardano-swaps
contracts enforce this behavior whenever possible. Here is the datum type:
-- | Swap Datum
type Price = Rational -- ^ askedAsset/offeredAsset
data SwapDatum = SwapDatum
{
swapPrice :: Price,
swapBeacon :: Maybe CurrencySymbol -- ^ Policy id for the DEX's beacon token
}
The Rational
type is a fraction (decimal types do not work properly). Fortunately, there is no loss of functionality from using fractions. Users are able to supply decimals to cardano-swaps
and it will properly convert the decimal to Rational
.
All prices in Cardano-Swaps are local (similar to limit orders in an order-book exchange). The price is always askedAsset/offeredAsset. For example, if $ADA is being offered for $DUST at a price of 1.5 (converted to 3/2), the contract requires that 3 $DUST are deposited for every 2 $ADA removed from the swap address. Ratios < 3/2 will fail, while ratios >= 3/2 will pass.
When engaging in swaps, it is only necessary that the desired swap ratio is met; not all assets at the swap address or UTxO must be swapped. For example, if there is 100 ADA in a swap address requesting 2:1 for DUST, a user may swap 20 ADA, as long as they return 80 ADA and 10 DUST in the same TX.
Since every user explicitly defines their desired swap ratios, oracles are not required. The "global" price naturally emerges where the local bids and asks meet - just like an order-book.
❗ A zero or negative price means the assets are effectively free. A malicious user may deposit a UTxO with a negative price in the datum in order to steal user funds. To prevent this, swaps will fail unless all prices are greater than 0.
❗ For user-friendliness, when ADA is part of the trading pair, the price MUST be in units of ADA. The swap contract will convert to Lovelace when necessary. If Lovelace units are entered, conversion will still take place and produce undesired behavior.
The swapBeacon
field for the datum prevents misuse of beacons. The contract forces all assets with the supplied policy-id to be burned instead of being withdrawn. This ensures the beacons can never be found in an address unrelated to cardano-swaps
. If the wrong policy id is supplied, assets will be locked forever. Only the UTxO containing the beacon needs to use Just beaconSym
; all active swaps can use Nothing
for this field. cardano-swaps
CLI handles this part of the datum automatically, preventing accidental locking.
Swap contracts have three possible actions, a.k.a. redeemers:
Close
- withdraw any UTxO located at the swap address and burn the beaconUpdate
- update the asking price for UTxOs at the swap addressSwap
- executing a swap with assets at the swap address
Only the owner (signified by the address' staking credential) is allowed to use the Close
or Update
redeemers. Anyone can use the Swap
redeemer.
The Close
redeemer allows the owner (signified by the address' staking credential) to recover the deposit stored with the reference script, and make the address undiscoverable by burning the beacon. In order to reclaim the deposit, the beacon must be burned. The requirements for successfully using the Close
redeemer are:
- All beacons among Tx inputs must be burned.
- The staking credential must signal approval:
- pubkey must sign
- script must be executed in the same tx
- Any new outputs to the address must contain the proper datum:
swapPrice
> 0swapBeacon
== Nothing
The Update
redeemer allows the owner to change the asking price of their position(s) by changing the inline datum attached of associated UTxOs. This action includes checks to ensure the new datum is properly used. The requirements for a successful update are:
- No beacons among tx inputs.
- The staking credential must signal approval:
- pubkey must sign
- script must be executed in the same tx
- All new outputs to the address must contain the proper datum:
swapPrice
> 0swapBeacon
== Nothing
The first requirement also means that the datum attached to the beacon's reference script cannot be updated. This is fine since that datum is never allowed in swaps.
The Swap
redeemer checks all of the assets leaving the swap address and all of the assets entering the swap address. For a successful swap, all of the following must be true:
- No swap input utxos have reference scripts - this also protects the beacon.
- All swap input prices are > 0.
- All outputs to the swap address contain the proper datum:
swapPrice
== weighted avg price of all swap inputsswapBeacon
== Nothing
- Only the offered asset (as defined in
SwapConfig
) is leaving the swap address. - QuantityOfferedAssetTaken * weighted average price <= quantityAskedAssetGiven
Custom error messages are included to help troubleshoot why a swap failed. The weighted average price must match exactly what the swap contract calculates. To help with this, cardano-swaps
can calculate the weighted price for you. The function cardano-swaps
uses is the same function the on-chain swap contract uses.
Here are some of the most impactful features of Cardano-Swaps:
Since each user has their own swap address, the process of delegation is identical to that of a normal address (either by staking key or staking script).
Check out the delegation section of GettingStarted.md for an example using a staking pubkey.
Since multiple swaps are combinable into a single transaction, any arbitrarily complex swap transaction can be created. The only limit is the size of the transaction itself.
Do you want to convert 10 ADA into 5 DUST and 5 AGIX? No problem! This can be done in one transaction. What about converting 10 ADA, 5 DUST, and 3 WMT into 16 AGIX and 1 of your favorite NFTs? Piece of cake!
By composing these swaps in one transaction, many-to-many multi-asset swaps are possible. The only limits are the maximum transaction limits for Cardano.
Liquidity in cardano-swaps is an emergent property; it arises from the (healthy) incentive for arbitragers to engage in complex swap transactions. Discrete liquidity pools are unnecessary.
Alice has 10 ADA in her swap address and is willing to swap them for 0.5 AGIX/ADA.
Bob has 10 AGIX in his swap address and is willing to swap them for 1 ADA/AGIX.
In this example, Charlie can profitably arbitrage and fulfill both of these swaps like this:
Charlie looks up all swap addresses willing to swap AGIX/ADA. Charlie finds Alice's address.
Charlie looks up all swap addresses willing to swap ADA/AGIX. Charlie finds Bob's address.
Using Bob's reference script, Charlie gives Bob 10 ADA and receives 10 AGIX.
Using Alice's reference script, Charlie gives Alice 5 AGIX and receives 10 ADA.
Charlie now has his original 10 ADA plus an additional 5 AGIX.
This all occurs in one transaction where Charlie pays the transaction fee.
On net, Charlie pays the transaction fees and receives 5 AGIX in return, while both Alice and Bob's swaps are fulfilled.
Alice has 10 ADA in her swap address and is willing to swap them for 1 DUST/ADA.
Bob has 10 DUST in his swap address and is willing to swap them for 0.5 AGIX/DUST.
Charlie has 10 AGIX in his swap address and is willing to swap them for 1 HOSKY/AGIX.
Mike has 10 HOSKY in his swap address and is willing to swap them for 1 ADA/HOSKY.
In this example, Sarah can profitably arbitrage and fulfill all of these swaps like this:
Sarah looks up all swap addresses willing to swap DUST/ADA. Sarah finds Alice's address.
Sarah looks up all swap addresses willing to swap AGIX/DUST. Sarah finds Bob's address.
Sarah looks up all swap addresses willing to swap HOSKY/AGIX. Sarah finds Charlie's address.
Sarah looks up all swap addresses willing to swap ADA/HOSKY. Sarah finds Mike's address.
Sarah gives Mike 10 ADA and receives 10 HOSKY.
Sarah gives Charlie 10 HOSKY and receives 10 AGIX.
Sarah gives Bob 5 AGIX and receives 10 DUST.
Sarah gives Alice 10 DUST and receives 10 ADA.
Sarah now has her original 10 ADA plus an additional 5 AGIX.
This all occurs in one transaction where Sarah pays the transaction fee.
On net, Sarah pays the transaction fees and receives 5 AGIX in return, while four swaps are fulfilled.
As shown in the realistic example, Sarah fulfills both the AGIX/DUST swap and the HOSKY/AGIX swap by "passing through" those pairs on her way back to ADA. As long as the entry and exit pairs (in this case ADA/HOSKY and DUST/ADA) have enough liquidity, arbitragers can spread that liquidity into less liquid swap pairs.
As a bonus, the very nature of illiquidity implies great arbitrage opportunities. The more illiquid a swap pair, the greater the potential arbitrage profits. Participating in arbitrage is permissionless, so anyone can design their own algorithms for finding the most profitable "path" through the currently available swaps.
Upgrades to cardano-swaps
can propagate through the ecosystem of users in a similarly democratic fashion as SPOs upgrading their pools to a new version of cardano-node
. Since users can close their swaps at any time, whenever there is a potential upgrade, users can choose to close their current swaps and recreate them with the new contracts. The main challenge here is the bifurcation of liquidity that occurs during upgrade periods. However, the more users there are and the more overall liquidity there is, the more this issue is minimized.
Thanks to the query-ability of beacon tokens, it is trivial for any frontend to integrate with Cardano-Swaps. For example, any wallet can integrate cardano-swaps
by adding support for querying the beacon tokens. They can also add their own user friendly way to create and use swaps. The only requirement is that all frontends/users agree to use the same beacon token standard. There is no need for risky extensions or dedicated frontends.
Basic benchmarking tests were done to determine the maximum number of inputs or outputs that the spending script can handle in one transaction.
Currently, Plutus is limited in that a spending script is always executed once for every script input, even if the inputs come from the same script address. So if there are 5 inputs from one of the script addresses, the spending script will be executed 5 times. The latter 4 are completely redundant in this situation. These redundant executions impact the maximum number of inputs and outputs that can fit in one transaction. There is a Cardano Problem Statement (CPS) looking to address this (here). Nonetheless, basic benchmarking results are shown here:
For opening a new address, I was successfully able to mint the beacon, store the reference script, and create 80+ new swap positions in one transaction.
Number of New Swaps | Tx Fee |
---|---|
5 | 0.647711 ADA |
10 | 0.697339 ADA |
15 | 0.747768 ADA |
20 | 0.797797 ADA |
25 | 0.847869 ADA |
For closing a new address, I was successfully able to burn the beacon, remove the reference script, and close 8 open positions in one transaction.
Number of Closed Swaps | Tx Fee |
---|---|
1 | 0.490071 ADA |
2 | 0.574382 ADA |
3 | 0.674041 ADA |
4 | 0.789050 ADA |
5 | 0.919407 ADA |
6 | 1.065113 ADA |
7 | 1.226169 ADA |
8 | 1.402573 ADA |
For updating open swaps, I was successfully able to update 5 positions and recreate them in one transaction. If you consolidate your open positions into one output, you may be able to update more in one transaction.
Number of Swaps Updated | Tx Fee |
---|---|
1 | 0.250309 ADA |
2 | 0.381332 ADA |
3 | 0.574544 ADA |
4 | 0.829945 ADA |
5 | 1.147533 ADA |
For swapping assets, I was successfully able to chain together 4 swap utxos before the redundant executions caused the transaction to exceed the memory limit.
Number of Swaps Chained | Tx Fee |
---|---|
1 | 0.313006 ADA |
2 | 0.511202 ADA |
3 | 0.772815 ADA |
4 | 1.114271 ADA |
The spending script gets the staking credential from the address of the UTxO being spent at run-time. When an owner related action is being performed (closing or updating positions), the spending script requires that the staking credential "signals approval" of the action:
- If the staking credential is a pubkey, then the staking pubkey must sign the transaction.
- If the staking credential is a script, then the script must be executed in the same transaction.
- the staking credential effectively becomes the "owner" for all actions except for the actual swap execution, in which case the spending credential is used directly.
It is possible to execute a staking script even if 0 ADA is withdrawn from a reward address. The only requirement to use staking scripts like this is that the associated stake address must be registered and delegated. Stake addresses can be utilized this way as soon as the registration+delegation transaction is added to the chain. There is no epoch waiting-period.
cardano-swaps
relies on the usage of beacon tokens to "tag" addresses, which demarcates them as distinct for efficient off-chain querying/aggregation. However, address distinction is impossible if each script address is composed of completely unique credentials. In other words, for Beacon Tokens to work properly, there must be a clear distinction between addresses participating in a given dApp and all other addresses. cardano-swaps
leverages the dual payment-staking credentials of Cardano addresses to maintain address-distinction (shared spending scripts) without sacrificing self-custody (unique staking keys/scripts). For further clarification, refer to the Beacon Token CIP
📌 v1.0.0 of cardano-swaps used unique spending scripts; you can read about the limitations in the v1.0.0 commit README.
If all users have direct access to the spending script for that trading pair, why are reference scripts used?
To disincentivize beacon bloat.
The reason is to incentivize users to actually use the DEX as intended. If there were no deposits, users can create a live address with a beacon token and then just leave it open even if they aren't actually using the address anymore (like if there are no swappable assets in it). This "zombie" address will still appear when querying the beacon tokens even though other users can't actually do anything with it. By requiring the deposits, users are incentivized to close unused addresses in order to get the deposits back.
Recall the contrived example above. What would happen if Charlie and Mike try to arbitrage it at the same time?
- Charlie and Mike both build their transactions since the UTxOs still exist.
- Charlie and Mike submit their transaction at the same time.
- Charlie's is added to a block first.
- When Mike's transaction is then picked to go into a block, the UTxOs no longer exist. The transaction fails without needing to run the swap contracts.
Since Mike's transaction will fail without needing to run the swap script, Mike's collateral is safe. Further, the more available swaps there are, the less likely these "collisions" will occur.
📌 future iterations of Ouroboros (namely Leios) may allow arbitragers to further limit these collisions by segmenting transactions among sharded mempools.
Yes. Yes it will. TVL is a silly metric. It is a measure of who can be most inefficient with DeFi capital.
The cardano-swaps protocol has all of the desired properties of a highly scalable DEX. Thanks to the use of beacon tokens, decentralization is no longer limited by the design of DEXs. Instead, the limiting factor is now the off-chain querying. However, innovations in this space are still in the early days. The Koios API is an example of a more decentralized off-chain platform. As the technology improves, the decentralization of this protocol will improve as well.