An Orderbook DEX Design Using Cobuild OTX

In this post, we will discuss what an orderbook-based dex might be like, if we leverage CKB’s cobuild OTX design. For simplicity, we will simply name our dex as dex1, which denotes that it’s the first prototype dex here, so as to inspire your own dex.

Some of our design goals include:

  • Unlike typical CEX one might have used, dex1 enables truly-decentralized design: one is free to send their order to any potential party that is willing to process the order. One matching engine doing censorship will at most be able to impact a portion of all the orders.
  • A panic button is provided: one can simply cancel all their submitted orders to all matching engines for the dex1.
  • While a decentralized setup certainly costs some efficiency, dex1 does also provide Web2-style, instant operations off-chain, by sacrificing certain decentralized properties at a local corner.

Some might already have an idea what the design feels like given the above design goals, but for the sake of completeness, let’s still dive into the discussion.

Naive Solution

One can sign the following Cobuild OTX for a naive orderbook based dex:

inputs:
  input 0:
    capacity: 200 CKB
    lock: Alice
    type: USDC UDT type script
    data: <2000 USDC>
outputs:
  output 0:
    capacity: 200 CKB
    lock: Alice
    type: USDT UDT type script
    data: <2010 USDT>
witnesses:
  witness 0: WitnessLayout format, OTX variant
    seals:
      0: SealPair format
        script_hash: Alice's script hash
        seal: <Signature for Alice>
    input_cells: 1
    output_cells: 1
    cell_deps: 0
    header_deps: 0
    message: Message format

This OTX asks for 2010 USDT in exchange for USDC. There is no external CKB script needed, anyone can sign such an OTX doing exchanges for any particular kinds of token issued on CKB. It is possible to use CKB as well, for example, this following alternative OTX asks for 20000 CKB in exchange of 5000 USDC:

inputs:
  input 0:
    capacity: 200 CKB
    lock: Alice
    type: USDC UDT type script
    data: <5000 USDC>
outputs:
  output 0:
    capacity: 20200 CKB
    lock: Alice
    type: <EMPTY>
    data: <EMPTY>
witnesses:
  witness 0: WitnessLayout format, OTX variant
    seals:
      0: SealPair format
        script_hash: Alice's script hash
        seal: <Signature for Alice>
    input_cells: 1
    output_cells: 1
    cell_deps: 0
    header_deps: 0
    message: Message format

Cobuild alone ensures that tokens are exchanged in a secure way.

But this is not a very useful dex, it has many drawbacks, just to name a few(note this list is by no means comprehensive):

  • It only allows limit orders, no market order is permitted. In other words, the ask price must be a definite number, there is no way one can specify a range of ask prices.
  • One can only fulfill the whole order at the same time, there is no way to do partial fills.

This works as a solution when 2 people want to exchange tokens casually(for example, the OTX can be generated by one, send to the other via emails or IMs, then the second user fulfills the OTX, generating a final CKB transaction). But it is certainly not good enough when we look at mature CEX doing the same features. We will need a better solution here.

Let’s Introduce A Validating Script

If we think about it, there might certainly be auxiliary data that requires validation for an on-chain dex:

  • Correct payment of orders using (slight) variable prices. Market order can be one such an example. We might need a solution like AMM DEX.
  • Partial fills might also need certain rules.
  • One might want to add deadlines to certain orders(i.e., when trading US stocks, there is an option to set an order that is valid on the same day, but expired the next).

We will introduce a special entity cell to dex1. The dex1 entity cell shares the following properties:

  • Each unique deployment of dex1, has its unique dex1 entity cell.
  • Depending on the mindset, a dex1 entity cell can have 2 different structures:
    • Its lock script contains a special dex1 entity script, doing all dex1 related validation work
    • Its lock script contains an always-success script, while its type script contains the dex1 entity script

Later we shall see, dex1 entity script is crucial to building a feature-rich orderbook-based dex.

Basic Orders

To use dex1, a user signs the following cobuild-compatible OTX so as to submit a basic limit order:

inputs:
  input 0:
    capacity: 200 CKB
    lock: Alice
    type: USDC UDT type script
    data: <2000 USDC>
outputs:
witnesses:
  witness 0: WitnessLayout format, OTX variant
    seals:
      0: SealPair format
        script_hash: Alice's script hash
        seal: <Signature for Alice>
    input_cells: 1
    output_cells: 0
    cell_deps: 0
    header_deps: 0
    message: Message format
      Action 0:
        script_hash: <dex1 entity script hash>
        script_info_hash: <dex1 dapp info hash>
        data: Dex1Action format
          order: Order format
            bid_token: <USDC UDT type script hash>
            ask_token: <USDT UDT type script hash>
            ask_amount: <2010 USDT>

Following the same convention introduced by Approved Action, this OTX introduces a new cobuild Action for dex1 entity script. The action contains order information, denoting that current OTX asks for 2010 USDT in exchange of 2000 USDC. The dex1 entity script, when executing, will then iterate through all OTXs in the whole CKB transaction, extract each cobuild action for itself, then perform the necessary validation work.

DEX1 Details, Version 1

To be able to process the above OTX, dex1 would have the following cobuild schema definition

table Order {
    bid_token: Byte32,
    ask_token: Byte32,
    ask_amount: Uint128,
}

table Dex1Action {
    order: Order,
}

For each dex1 deployment, there will be one unique dex1 entity cell on chain, with its own unique lock script or type script(the above mentioned dex1 entity script hash). But there could be any number of OTX processors that can assemble OTXs to CKB transaction, and in the mean time, consume and then re-create the dex1 entity cell. One OTX processor, can in fact create CKB transaction like following for CKB:

inputs:
  input 0(otx #0):
    capacity: 200 CKB
    lock: Alice
    type: USDC UDT type script
    data: <2000 USDC>
  input 1(otx #1):
    capacity: 200 CKB
    lock: Bob
    type: USDC UDT type script
    data: <1500 USDC>
  input 2(otx #2):
    capacity: 200 CKB
    lock: Charlie
    type: USDT UDT type script
    data: <3000 USDT>
  input 3(otx #2):
    capacity: 200 CKB
    lock: Charlie
    type: USDT UDT type script
    data: <530 USDT>
  input 4:
    capacity: 1000 CKB
    lock: always success
    type: DEX1 entity script
    data: DEX1 entity data
  input 5(fee paying cell from OTX processor):
    capacity: 12345.1234 CKB
    lock: OTX processor 1
    type: <EMPTY>
    data: <EMPTY>
outputs:
  output 0:
    capacity: 1000 CKB
    lock: always success
    type: DEX1 entity script
    data: DEX1 entity data
  output 1:
    capacity: 200 CKB
    lock: Alice
    type: USDT UDT type script
    data: <2010 USDT>
  output 2:
    capacity: 200 CKB
    lock: Bob
    type: USDT UDT type script
    data: <1520 USDT>
  output 3:
    capacity: 200 CKB
    lock: Charlie
    type: USDC UDT type script
    data: <3500 USDC>
  output 4:
    capacity: 12345.1233 CKB
    lock: OTX processor 1
    type: <EMPTY>
    data: <EMPTY>
witnesses:
  witness 0: WitnessLayout format, OTX variant(otx #0)
    seals:
      0: SealPair format
        script_hash: Alice's script hash
        seal: <Signature for Alice>
    input_cells: 1
    output_cells: 0
    cell_deps: 0
    header_deps: 0
    message: Message format
      Action 0:
        script_hash: <dex1 entity script hash>
        script_info_hash: <dex1 dapp info hash>
        data: Dex1Action format
          order: Order format
            bid_token: <USDC UDT type script hash>
            ask_token: <USDT UDT type script hash>
            ask_amount: <2010 USDT>
  witness 1: WitnessLayout format, OTX variant(otx #1)
    seals:
      0: SealPair format
        script_hash: Bob's script hash
        seal: <Signature for Bob>
    input_cells: 1
    output_cells: 0
    cell_deps: 0
    header_deps: 0
    message: Message format
      Action 0:
        script_hash: <dex1 entity script hash>
        script_info_hash: <dex1 dapp info hash>
        data: Dex1Action format
          order: Order format
            bid_token: <USDC UDT type script hash>
            ask_token: <USDT UDT type script hash>
            ask_amount: <1520 USDT>
  witness 2: WitnessLayout format, OTX variant(otx #2)
    seals:
      0: SealPair format
        script_hash: Bob's script hash
        seal: <Signature for Bob>
    input_cells: 2
    output_cells: 0
    cell_deps: 0
    header_deps: 0
    message: Message format
      Action 0:
        script_hash: <dex1 entity script hash>
        script_info_hash: <dex1 dapp info hash>
        data: Dex1Action format
          order: Order format
            bid_token: <USDT UDT type script hash>
            ask_token: <USDC UDT type script hash>
            ask_amount: <3500 USDC>  

This CKB-legit transaction packs the above OTX, together with other OTXs, so the actual orderbook orders are processed and submitted on chain. Dex1 entity script included by dex1 entity cell will iterate through each OTX, locating actions for dex1 entity script, validating that each OTX will receive its designated tokens(output cells #1 - #3) in current transaction.

In this sense, dex1 entity cell enables permission-less dex on chain. While individual OTX processors might gather different subset of OTXs, some might be evil to do censorship in its own processor, but a user is always free to send their OTXs to any(or many) of the OTX processors for processing, essentially achieving the same level of censorship-resistance as typical CKB transactions.

Market Order

Another popular used function in an orderbook dex, is market order: one shall be able to just submit an order to buy/sell using the currently optimal price. We can enhance our dex with this feature:

inputs:
  input 0:
    capacity: 200 CKB
    lock: Alice
    type: USDC UDT type script
    data: <2000 USDC>
outputs:
witnesses:
  witness 0: WitnessLayout format, OTX variant
    seals:
      0: SealPair format
        script_hash: Alice's script hash
        seal: <Signature for Alice>
    input_cells: 1
    output_cells: 0
    cell_deps: 0
    header_deps: 0
    message: Message format
      Action 0:
        script_hash: <dex1 entity script hash>
        script_info_hash: <dex1 dapp info hash>
        data: Dex1Action format
          orders: Order format, market Order variant
            bid_token: <USDC UDT type script hash>
            ask_token: <USDT UDT type script hash>

This OTX only instructs dex1 that Alice wants to buy USDT at the best market price with 2000 USDC. It depends on current market price to say how many USDTs are gonna traded to Alice.

DEX1 Details, Version 2

To support market order, we will need slight modification to dex1’s cobuild schema:

table LimitOrder {
    bid_token: Byte32,
    ask_token: Byte32,
    ask_amount: Uint128,
}

table MarketOrder {
    bid_token: Byte32,
    ask_token: Byte32,
}

union Order {
    LimitOrder,
    MarketOrder,
}

table Dex1Action {
    order: Order,
}

This way a user can specify the order type, and dex1 entity script will validate according to different order types.

Or it’s possible to provide market order with a minimum_ask in case liquidity is very bad for certain tokens:

table LimitOrder {
    bid_token: Byte32,
    ask_token: Byte32,
    ask_amount: Uint128,
}

table MarketOrder {
    bid_token: Byte32,
    ask_token: Byte32,
}

table MarketOrderWithMinimumAsk {
    bid_token: Byte32,
    ask_token: Byte32,
    minimum_ask: Uint128,
}

union Order {
    LimitOrder,
    MarketOrder,
    MarketOrderWithMinimumAsk,
}

table Dex1Action {
    order: Order,
}

Problem: Censorship of Market Orders

There is certain one problem with limit order: the actual orderbook is maintained off-chain by each OTX processors. When dex1 entity script validates a market order, it has no way of determining if the current price is indeed the optimal price of the OTX processor creating current transaction. There is indeed some basic checks that a dex1 entity script can do: for example, if OTX #2 contains a market order, in which Alice buys 1.01 USDC per USDT, dex1 entity script can validate that all OTXs that come after OTX #2 in current transaction, must have a price that is higher than 1.01 USDC per USDT. But still, there is no way for dex1 entity script to know that current OTX processor might have orders off-chain that sells USDC at a price of, say, 1.08 USDC per USDT.

And we have arrived the first trade-off we made in dex1: for an off-chain design, it is only natural that censorship might exist at a certain misbehaving OTX processor. We can only design an architecture in which that multiple OTX processors coexist, and one is free to submit their order to any of the OTX processors, or multiple OTX processors at the same time. There are mitigations available, but they are by no means eliminating all censorship:

  • A mature OTX processor will provide some sort of UIs for one to preview current market order price, and if a fulfilled market order price is way-off from the previewed price, one is always able to ditch this OTX processor in favor or another at zero cost. Dex1 works in a way that one does not need to make deposits (like current CEX) before trading, if one does not like a particular OTX processor, one can simply switch to another one for the next order at no cost at all.
  • A MarketOrderWithMinimumAsk order type can always provide an extra layer of security, ensuring the worst case scenarios.

Rethinking OTX

Historically, when we are designing different versions of Open Transactions(OTX) on CKB, we had the vision that where we can use OTX to fixate certain input cells as well as output cells, but allowing changing of other parts in a transaction. We were hoping we can use signatures to guard the computed result of an OTX directly. I do want to point out here that as we learned more about OTX, we are experimenting a different design here: with Approved Actions, OTXs, in the cobuild era, are more used to guard the amount of tokens consumable by a transaction. The computed result is typically not included in the OTXs(see the above OTXs, they all have zero output cells). Instead, a cobuild Action is typically used to provide parameters, which are then used by an on-chain script(entity script) to do the actual validations, ensuring the final results contained in the CKB transaction confront to the parameters in OTXs.

One could argue that both styles has their own pros and cons. It’s just worth mentioning here, that cobuild OTXs are really just experimenting a different direction here. And we really can mix the two together: when computed results are available at OTX creation time(i.e., limit orders), we can definitely include the results as output cells in OTXs, but for other type of OTXs(i.e., market orders), computed results are only available at final OTX assembling time.

Cancelling Orders

Another popular topic is: how can one cancel orders after submitting?

In a production setup of dex1, there will be one dex1 entity cell that is available on-chain. Multiple OTX processors can take turn submit CKB transactions updating this dex1 entity cell in turn, each submitting different OTX orders to CKB. Each of the OTX processor, in a way, can be seen as an individual exchange, it might have its own designed UI for visualizing orderbook to the user, and accept user operations as OTXs. It would also have its own storage containing current orderbooks. Optionally, different OTX processors can exchange orders made by different users via a P2P network.

The optimal performance can only be achieved when each OTX processor handles order cancellation locally by itself. In other words, we do expect that in dex1, each OTX processors would accept commands from users to cancel already submitted order(the optional P2P network might relay such cancellation commands as well). Nothing else beats the performance of this Web2-style design. However, there is always the issue of censorship: when a user created an order they regretted so much, simply asking the OTX processors to cancel that order does not provide enough confidence. In this case, the user can choose to create a new CKB transaction, this CKB transaction can only use one of the input cells included in the submitted OTX to cancel, and simply transfer this cell to the user themselves. The goal of this transaction, is to invalidate one of the OutPoint used by the OTX, essentially creating a double-spent issue, rendering the OTX invalid. When this new CKB transaction is accepted on chain, the user can have 100% guarantee that their previously submitted OTX(s) are successfully terminated, even in the presence of censorship.

And this design, is really fundamental to dex1: in typical cases, we resort to Web2-style design for better performance, but there is always a panic button which will be more expensive and less performant(an on-chain operation is required here), but provides perfect guarantee in the presence of censorship.

Multiple Orders

Of course one can create multiple OTXs for multiple different orders, but that could potentially be quite wasteful in resource:

  • Assuming Alice has one cell containing 2000 USDC, what if she wants to create an order to by UDST from 1200 USDC, and another order to by CKB from 300 USDC?
  • Assuming Alice wants to provide 200 CKB to hold USDC she bought, and Alice only has a big cell of 1 million CKBs, creating an OTX with 1 million CKBs as one input, and 999,800 CKB as one output cell, can in fact be quite wasteful in terms of CKB

We can expand previous dex1 design, so one OTX can in fact keep more than one order:

inputs:
  input 0:
    capacity: 200 CKB
    lock: Alice
    type: USDC UDT type script
    data: <2000 USDC>
outputs:
witnesses:
  witness 0: WitnessLayout format, OTX variant
    seals:
      0: SealPair format
        script_hash: Alice's script hash
        seal: <Signature for Alice>
    input_cells: 1
    output_cells: 0
    cell_deps: 0
    header_deps: 0
    message: Message format
      Action 0:
        script_hash: <dex1 entity script hash>
        script_info_hash: <dex1 dapp info hash>
        data: Dex1Action format
          orders:
            0: Order format, market Order variant
              bid_token: <USDC UDT type script hash>
              bid_amount: <1200 USDC>
              ask_token: <USDT UDT type script hash>
            1: Order format: limit Order variant
              bid_token: <USDC UDT type script hash>
              bid_amount: <300 USDC>
              ask_token: <all zeros, denoting CKB>
              ask_amount: <3000 CKB>

The above OTX contains 2 orders:

  • Asking for USDT using best market order using 1200 USDC
  • Asking for 3000 CKB using 300 USDC

In the previous section, we discussed that a user should first have a Web2-style relationship with an OTX processor, allowing a user to request the OTX processor to cancel a certain order. The user, of course still has the ability to force cancelling an order by sending an on-chain transaction. We can expand this relationship here: in most cases, an OTX processor can maintain at most one OTX sent from a user. A user can then send an updated OTX(should have overlapping input cells with previous OTX) to add more orders, or cancel existing orders. At times, the OTX processor might choose to assemble this OTX with other OTXs into a full CKB transaction to be accepted on chain, at this stage, the user can think submitted orders have been accepted. Before submitted, the user can always send more requests to the OTX processor demanding updating the orders included in the OTX, the user is also free at any point to send a CKB transaction to CKB so as to invalid the OTX containing the orders.

DEX1 Details, Version 3

To support multiple orders, we will need modifications to dex1’s cobuild schema:

table LimitOrder {
    bid_token: Byte32,
    bid_amount: Uint128,
    ask_token: Byte32,
    ask_amount: Uint128,
}

table MarketOrder {
    bid_token: Byte32,
    bid_amount: Uint128,
    ask_token: Byte32,
}

table MarketOrderWithMinimumAsk {
    bid_token: Byte32,
    bid_amount: Uint128,
    ask_token: Byte32,
    minimum_ask: Uint128,
}

union Order {
    LimitOrder,
    MarketOrder,
    MarketOrderWithMinimumAsk,
}
vector Orders <Order>;

table Dex1Action {
    orders: Orders,
}

Partial Fills

There is one problem: what if an OTX processor can only partially fulfill an order? The design to introduce multiple orders also makes this problem even worse: consider an OTX with 3 orders, what if 2 orders can be assembled with other orders, but the remaining one will have to wait?

First, I do want to mention that different products made by different people, will always have different design considerations. Just look at all the existing exchanges we have, they all fulfill the same core features, while each of them will differ at subtle places.

And the same thing happens to dex1 design, chances are you might want certain features in a different way and that totally works. So for a trickier one like partial fills, we will simply give several options, it depends on preferences to tell which one might be better to employ:

  • One can simply disable partial fills at all, an order will either be filled, or not
    • For multiple orders, dex1 can choose to fill those one that it can, and refund the rest back to the user, the user can then aim to create a new order
  • A slightly complicated solution, is that a new lock can be introduced here. So when an OTX is partially filled, the remaining tokens, whether they come from partially fill of a particular order, or they come from the unfilled order in multiple orders, can be collected into an output cell using the new lock. The new lock can be unlocked either by the original user(cancelling an order), or be used in later orderbook filling transactions. In this case, a complete orderbook will contain both the off-chain OTXs, and some on-chain cells that are generated from partially fills. Both will be processed by OTX processors, and can be validated by on-chain dex1 entity script. The overall process continues to work without hassles.

Orders With Deadlines

It’s also possible that some orders come with deadlines. For example, many platforms allows one to create an order that is only valid within the trading day for US stocks, we can build a similar feature for dex1:

inputs:
  input 0:
    capacity: 200 CKB
    lock: Alice
    type: USDC UDT type script
    data: <2000 USDC>
outputs:
witnesses:
  witness 0: WitnessLayout format, OTX variant
    seals:
      0: SealPair format
        script_hash: Alice's script hash
        seal: <Signature for Alice>
    input_cells: 1
    output_cells: 0
    cell_deps: 0
    header_deps: 0
    message: Message format
      Action 0:
        script_hash: <dex1 entity script hash>
        script_info_hash: <dex1 dapp info hash>
        data: Dex1Action format
          orders:
            0: Order format, limit Order with deadline variant
              bid_token: <USDC UDT type script hash>
              bid_amount: <2000 USDC>
              ask_token: <USDT UDT type script hash>
              ask_amount: <2015 USDT>
              deadline: <4.5 epochs from epoch 1800, or roughly 18 hours>

Here an OTX creates an order asking 2015 USDT for 2000 USDC, which is valid in the next 18 hours.

Dex1 entity script handles the validation of deadlines, in the following way:

  • Each OTX assembling transaction consuming the dex1 entity script, will need to augment the transaction with the block header in which the dex1 entity input cell was originally committed.
  • When validating orders, each order with deadlines, must use a deadline that is not after the block in which dex1 entity cell was last committed.

This way deadline in implemented, and we have arrived our final design of dex1.

DEX1 Details, Version 4 (current final one)

Here’s the schema for current dex1 design:

table LimitOrder {
    bid_token: Byte32,
    bid_amount: Uint128,
    ask_token: Byte32,
    ask_amount: Uint128,
}

table LimitOrderWithDeadline {
    bid_token: Byte32,
    bid_amount: Uint128,
    ask_token: Byte32,
    ask_amount: Uint128,
    deadline: Uint64,
}

table MarketOrder {
    bid_token: Byte32,
    bid_amount: Uint128,
    ask_token: Byte32,
}

table MarketOrderWithMinimumAsk {
    bid_token: Byte32,
    bid_amount: Uint128,
    ask_token: Byte32,
    minimum_ask: Uint128,
}

union Order {
    LimitOrder,
    LimitOrderWithDeadline,
    MarketOrder,
    MarketOrderWithMinimumAsk,
}
vector Orders <Order>;

table Dex1Action {
    orders: Orders,
}

CKBytes Requirements

You might have noticed that we have refrained from the discussion of storage requirement: when you ask for token A in an order, where will the CKBytes required to store token A be from? In dex1, we are simplifying this requirement with one rule: the order creator, or the OTX creator, is in charge of providing CKBytes for any potential tokens they get. For example, if one wants to ask 3000 USDT for 30000 CKB, the following OTX might be created:

inputs:
  input 0:
    capacity: 10000 CKB
    lock: Alice
    type: <EMPTY>
    data: <EMPTY>
  input 1:
    capacity: 22200 CKB
    lock: Alice
    type: <EMPTY>
    data: <EMPTY>
outputs:
  output 0:
    capacity: 2000 CKB
    lock: Alice
    type: <EMPTY>
    data: <EMPTY>
witnesses:
  witness 0: WitnessLayout format, OTX variant
    seals:
      0: SealPair format
        script_hash: Alice's script hash
        seal: <Signature for Alice>
    input_cells: 2
    output_cells: 1
    cell_deps: 0
    header_deps: 0
    message: Message format
      Action 0:
        script_hash: <dex1 entity script hash>
        script_info_hash: <dex1 dapp info hash>
        data: Dex1Action format
          orders:
            0: Order format, limit Order variant
              bid_token: <all zeros, denoting CKB>
              bid_amount: <30000 CKB>
              ask_token: <USDT UDT type script hash>
              ask_amount: <3000 USDT>

While the order specifies that only 30000 CKBytes are paid, the OTX actually provides 30200 CKBytes, setting aside 200 CKBytes for the storage requirement of the returned 3000 USDT.

Product Side Solutions

Of course, we do want to mention that the above design, is one possible design of building an orderbook dex following cobuild OTX design. Different products can vary in different ways. And it is possible and extremely likely you would want to alter the design here in a way to suit your preference better. Some (but not necessarily all) examples might include:

  • Some would want a stronger guarantee when cancelling an order, than simply the OTX processor say so, but they would not want the full effort of creating an on-chain transaction. In this sense, one can modify the design so when an order is cancelled, the OTX processor sends a signature ensuring that an order (denoted by the tx hash) cannot land on chain. If later the OTX processor misbehaves and packs this order, the user can used the obtained signature to punish the OTX processor(for instance, a portion of staked tokens can be granted to the user from the OTX processor).
  • Some might want a different solution to the CKBytes requirements
  • Others might want to maintain more OTXs between a user and an OTX processor to accommodate more scenarios

Given those consideration, we are merely putting together a design that provides an inspiration, and really different people might want different end forms in their products. And this is totally okay. We just want to provide a starting point on what a dex on cobuild OTX should be like.

Recap

Our orderbook dex here is designed with one thing in mind: it should be available forever. In the happy case, our dex should work much like a typical cex in the Web2 genre. One can enjoy highly performant solutions that are not unlike what we have right now. And this might be your experience 95% of the time. What truly matters here, is that in the rare 5% of the case when shit happens, our dex empowers users with the same functionality, though in a less-performant way, so trading continues to work. This is really what dex1 stands out compared to old, Web2-style apps, and what we want to aim for, for future cobuild compatible apps.

8 Likes