Current methods of exchange Counterparty assets with Bitcoin require either (1) multiple confirmations (via BTCPay on the DEx) or (2) a trusted seller (via dispensers). As noted by DerpHerpenstein,[1] the only way securely to achieve atomic, single-confirmation trades with BTC is by extending Counterparty to use the UTXO system of Bitcoin directly. It's also clear that the address-based system currently used by Counterparty is much more flexible and powerful. For example, if a person has several assets with different balances on the same UTXO, it becomes complicated to send only part of one of the assets. Another problem with both the current system and the one proposed by DerpHerpenstein is that multiple transactions are required to place or conclude an order. Finally, the management of PSBTs may require additional transactions to offer an alternative to centralized solutions.
The system proposed here attempts to resolve all of these problems: It aims to enable placing and concluding a BTC sale order in a single transaction per party, only storing escrowed assets for an order in UTXOs and offering an on-chain solution to store and revoke PSBTs. It offers the possibility of opening and executing an order equally well from across addresses and UTXOs (!) This design is also the fastest to develop and least intrusive, as it requires minimal changes to the existing codebase.
N.B. The user must have two UTXOs (from the same or from two different addresses) to be able to place a sell order: the first to build the PSBT transaction that will be completed by the buyer, and the second to build the transaction to open the sell order.
Transaction: Incomplete PSBT
Inputs | Outputs |
---|---|
<utxo_1> | |
Output Containing Asset for Sale | <payment_to_seller> |
Asset Price and Seller Address | |
<data_a> | |
Asset name, Quantity, Order Expiration Block (optional) |
Transaction: Sell Order
Inputs | Outputs |
---|---|
<utxo_2> | |
Seller Address | <data_b> |
Counterparty Prefix and ID, Transaction PSBT | |
Counterparty Nodes will parse this transaction and expose the embedded Incomplete PSBT via its API, with a unique identifier equal to the transaction hash of the sell order.
(If assets are already attached to <utxo_1>
the quantity of the sell order must be exactly equal to the quantity of attached assets; else, Counterparty nodes will record that the assets for sale are escrowed in <utxo_1>
.)
The buyer retrieves the Incomplete PSBT from the node API and completes it as follows:
Completed PSBT
Inputs | Outputs |
---|---|
<utxo_1> | |
Output Containing Asset for Sale | <payment_to_seller> |
Asset Price, Seller Address | |
<payment_from_buyer> | |
Buyer Address | <data_a> |
Asset Name, Quantity, Order Expiration Block | |
<data_c> | |
Destination Type (utxo xor address) | |
<change_to_buyer> | |
Buyer Address |
Upon seeing this transaction in the Bitcoin blockchain, Counterparty nodes verify:
- that the expiration_block has not yet passed, and
- that the order has not been canceled.
If these conditions are met, they release the assets escrowed in <utxo_1>
and credit the buyer's address / UTXO (depending on value of the destination_type
parameter).
To cancel an order, simply broadcast a classic Counterparty transaction:
Inputs | Outputs |
---|---|
Seller Address | |
Cancel Order Escrowed in utxo_1 | |
Two new contracts will be written:
atomicsell.py
— This contract allows construction and parsing of transactions to open a BTC sell order.atomicbuy.py
— This contract allows construction and parsing of transactions to execute a BTC buy order.
The cancel.py
contract will be modified to enable cancellation of an Atomic Swap order.
atomicsell.py
def prepare_compose(
db,
source: str,
quantity: int,
price: int,
expiration_block: int,
):
"""
Returns an unsigned PSBT transaction, to be signed and used with the `signed_psbt` parameter of the compose function
:param db: The database object.
:param source: The source address or UTXO (`<tx_hash>:<n>`).
:param price: The price of the assets in satoshis.
:param expiration_block: The block height at which the order will expire.
"""
def compose(
db,
signed_psbt: str,
):
"""
Return a raw transaction hex to be broadcasted.
:param db: The database object.
:param signed_psbt: The signed PSBT transaction returned by the `prepare_compose()` function.
"""
atomicbuy.py
def compose(
db,
source: str,
order_tx_hash: str,
destination_type: str,
):
"""
Return a raw transaction hex to be broadcasted.
:param db: The database object.
:param source: The source address of the transaction.
:param order_tx_hash: The transaction hash of the sell order.
:param destination_type: The type of destination. Can be `address` or `utxo`.
"""
- New Routes
- Get All Atomic Orders:
/v2/atomicorders
- Get Atomic Order:
/v2/atomicorders/<tx_hash>
- Get Atomic Orders by Asset:
/v2/assets/<asset>/atomicorders
- Prepare Atomic Sell:
/v2/addresses/<address>/compose/atomicsell/prepare
- Compose Atomic Sell:
/v2/addresses/<address>/compose/atomicsell
- Compose Atomic Buy:
/v2/addresses/<address>/compose/atomicbuy
- Get All Atomic Orders:
- A new table
atomicorders
will be created with the following fields:- tx_hash (Atomic Sell identifier)
- block_index
- source (source of the transaction)
- utxo
- asset
- quantity
- price
- expiration
- buyer (address or utxo)
- buy_tx_hash
- buy_block_index
- buy_source
- status (
open
,filled
,canceled
,expired
,invalid: <reasons>
)