Skip to main content

Chia Offers

Contents:

Introduction#

Offers enable peer-to-peer asset exchange in Chia's ecosystem. In other words, you can swap tokens that run on Chia's blockchain without needing to go through an exchange. Only two parties are required -- the Maker, who creates the offer, and the Taker, who accepts it.

The Maker and Taker don't need to trust each other. Any attempts to modify the offer will invalidate it.

Hypothetical example#

A clique of classical jazz enthusiasts creates a new token in Chia's ecosystem called "CAT King Cole" (CKC). Alice has 1 XCH in her wallet, and she wants to join the CKC club. The current exchange rate is 251 CKC per XCH.

Alice wallet before offer
Alice's wallet before creating the offer.

Alice likes those numbers, so she uses her Chia wallet to generate an offer file with the following conditions, to be enacted upon the offer's acceptance:

offer details
Alice's offer: 1 XCH for 251 CKC.

So far, this is not a valid transaction. She hasn't actually spent her XCH, and she can't create CKC out of thin air -- this isn't a jazz improv show. She needs someone to take the other side of this offer.

Alice scours the internet and finds a message board where fans of CKC like to share their latest riffs. She posts her offer file and waits, not knowing who will agree to face the music. However, she does know that potential Takers cannot modify the offer -- they must take it or leave it. Additionally, acceptance of the offer is on a first come, first served basis. They better act quickly!

The offer is not left dangling for long. After just a few seconds, Bob (who's more of a Cat Stevens fan) opens the offer file in his Chia wallet, which asks if he would like to exchange 251 CKC for 1 XCH. Bob accepts the offer, and his wallet automatically creates the other half of the transactions that Alice had started.

offer Bob view
Bob's view of the offer.

Alice posted her offer on a message board, not on a decentralized exchange. She could've posted the offer on Reddit, Twitter, Facebook, or anywhere else online.

Alice didn't trust Bob, and Bob didn't trust Alice. They never had to meet or interact in any way. If either of them had attempted to change the conditions of the offer, it automatically would've been rendered invalid. And as soon as Bob accepted the offer, the transactions became valid and were automatically processed, no middlemen required.

That's music to Alice and Bob's ears.

Alice post offer wallet
Alice's wallet after the offer has been accepted.
Bob post offer wallet
Bob's wallet after the offer has been accepted.

The rest of this document will go into the details of offers -- why they're valuable, how the offer files are created, design decisions, and some of the exciting possibilities that lie ahead.

If you want to start using offers right away, you can check out our tutorials:


Advantages#

Offers have many properties that we think will make them a valuable tool for Chia's community:

  • Secure: When using Chia offers, Makers and Takers retain control of their private keys, as well as their assets. By contrast, centralized exchanges require users to transfer their funds to an account that the exchange controls. If the exchange is hacked or simply goes out of business, users can lose their funds. With Chia offers, self-custody of keys and funds is assured.

    Offer files do not contain private keys or any way to deduce them. If an offer file falls into a "hacker's" hands, they only have two options: ignore the offer or accept it.

  • Immutable: Once an offer file is created, any alterations to the file will invalidate it. The offer file only has three possible outcomes:

    1. A Taker accepts the offer as-is. All transactions contained within it are automatically processed. (There also could be multiple Takers, explained below.)
    2. The Maker cancels the offer.
    3. The offer will be in a pending state until either 1. or 2. are fulfilled. It is possible that the offer never is completed or canceled.

    Takers are free to propose a counter offer by creating their own offer file. In this case, the original Maker could cancel the original offer, and both parties' roles would be reversed.

  • Compliant: As offers are inherently peer-to-peer, they don't constitute an “exchange” or other regulated market activity.

  • Trustless: Offers are not analogous to a handshake or a promise, where one party could renege on the trade. By using Chia offers, the Maker and Taker don't need to trust each other. They don't even need to know each other. As long as a Taker matches the offer identically, the offer will be valid.

  • Simultaneous: The Maker's and Taker's transactions must happen in the same block. In Chia, all transactions conducted within a single block happen simultaneously. This eliminates one type of Maximum Extractable Value (MEV), where validators can increase their fees by re-ordering transactions.

  • Non-custodial: Neither Maker nor Taker are required to send their funds to a trusted intermediary, such as an escrow service or an exchange. This removes the need for Over The Counter (OTC) desks and other middlemen, who require their customers to submit funds before they allow transactions to complete.

  • Commission-free: Because offers don't use escrow services or other middlemen, there are also none of the typical fees associated with those intermediaries. (Offers are subject to transaction fees, just like all transactions on Chia's network.)

  • Multi-asset: A Maker can create an offer for multiple assets on both ends of the offer. For example, they could offer 1 XCH and 1.75 CKC for 100,000 SBX and 3 MRMT.


Technical details#

In this section, we'll discuss the technical details of offers, including how they're created, accepted, canceled, etc.

Offer States#

An offer has six potential states, as defined in trade_status.py:

  1. PENDING_ACCEPT -- The Maker has created the offer, but a Taker has not yet accepted it. The Maker's wallet has reserved the coin(s) to be spent. The spendbundle for the offer has not been sent to the mempool.
  2. PENDING_CONFIRM -- The Taker has accepted the offer. The Taker's wallet has reserved the coin(s) to be spent. The completed spendbundle has been sent to the mempool.
  3. PENDING_CANCEL -- The Maker has attempted to cancel the offer by spending the coins being offered. The completed spendbundle has been sent to the mempool.
  4. CANCELLED -- Depending on which type of cancellation has been used, either:
    • The Maker's wallet has released the coins it had been reserving for this offer, or
    • The Maker's coins have been spent and new ones have been created in the Maker's wallet.
  5. CONFIRMED -- The Maker's and Taker's reserved coins have been spent in the same spendbundle. The offer has been completed successfully.
  6. FAILED -- The Taker attempted, and failed to accept the offer. This could have happened either because the Maker canceled the offer, or because another Taker took the offer first.

Creating an offer file#

Here's the basic workflow to create an offer file:

  1. The Maker uses either the wallet GUI or CLI to create the terms for an offer. For example, the Maker might offer 1 XCH for 251 CKC. If the Maker doesn't have sufficient funds, an error is thrown.

  2. The Maker's wallet selects the appropriate coin(s) to spend, starting with the largest coin available.

  3. For each coin the Maker wants to receive from the offer, the Maker's wallet creates a notarized coin payment. This is a list in the form of (PH1 AMT1 ...), where:

    • PH1 is the puzzle hash of the coin the Maker wants to acquire.
    • AMT1 is the value of the coin the Maker wants to acquire.
    • ... is an optional memo of arbitrary length. The trade manager adds a hint to itself in this memo.
  4. The Maker's wallet creates a nonce N, using the treehash of a sorted list of the coinIDs of each coin being offered.

    Every coinID needs to be included in the nonce to prevent the Maker from creating two offers that can both be completed with just one payment. (Note that even if two conflicting offers were created, the blockchain would correctly reject one of them as a double-spend.) Because each coinID must be unique, any attempts to change any of the coins being offered will cause the offer to become invalid.

    If you're unfamiliar with nonces, Wikipedia has a good explanation.

  5. The Maker's wallet combines the nonce (Step 4) with the notarized coin payment(s) (Step 3) to create a list called notarized_payments. For example, if three coins are included in the Maker's offer, notarized_payments will be structured like this: ((N . ((PH1 AMT1 ...) (PH2 AMT2 ...) (PH3 AMT3 ...))) ...).

  6. The Offer driver calculates the announcements that need to be asserted in order to get paid.

  7. The Maker's wallet creates a spendbundle paying the puzzlehash of settlement_payments.clvm (explained in the next section). Finally, the offer file is created, using notarized_payments and the spendbundle.

The offer file is now complete. The Maker can send this file anywhere others might see it, including social media, message boards, or a website dedicated to maintaining a list of current offers.

The offer's status is now PENDING_ACCEPT. In order for the offer to be completed, it still requires a CREATE_PUZZLE_ANNOUNCEMENT condition for the whole puzzle, and a CREATE_COIN condition for each type of asset to be received. The Maker's coin(s) can't be spent until a Taker creates these conditions.

settlement_payments.clvm#

Offers use a Chialisp puzzle called settlement_payments.clvm. This puzzle's solution is a list of notarized_payments, which were calculated in the previous section.

Recall that notarized_payments is structured like this: ((N . ((PH1 AMT1 ...) (PH2 AMT2 ...) (PH3 AMT3 ...))) ...), where:

  • N is the nonce.
  • PH1 is the puzzle hash of the first coin.
  • AMT1 is the amount (or value) of the coin being offered.
  • ... is an optional memo.

For each set of notarized coin payments, this puzzle creates one CREATE_PUZZLE_ANNOUNCEMENT condition. For each coin payment within this set, the puzzle creates one CREATE_COIN condition.

The reason for creating these conditions is to match the announcements created in the offer file. The settlement_payments puzzle is quite versatile -- it can be used as an inner puzzle inside a CAT or NFT, as a puzzle to spend regular XCH, or in order to spend any other assets in Chia's ecosystem.

Accepting an offer#

The offer file can be named anything, and it contains a bech32 address for an incomplete spendbundle. The Taker still must perform several steps before the offer can be confirmed:

  1. View the offer -- The Taker needs to validate the terms of the offer before choosing whether to accept it. This can be done using either Chia's wallet GUI or the CLI. In either case, the Taker can choose whether to load the offer file or paste its contents.

  2. Validate the offer -- When the offer is loaded into the Taker's wallet, the wallet verifies that the offer is valid by checking that the Maker's coins have not been spent. If any coins have been spent, the wallet will show an error that the offer is no longer valid. If the offer is still valid, the Taker's wallet displays the offer's terms and asks whether the Taker will accept it.

  3. Create a spendbundle -- If the Taker accepts, then the Taker's wallet creates a new spendbundle with a combination of both the Maker's and Taker's ends of the offer. The offer's status is now PENDING_CONFIRM.

  4. Create a CAT wallet (optional) -- The Taker's wallet creates a new CAT wallet if there isn't one already.

  5. Push the spendbundle -- The Taker's wallet pushes the spendbundle to the blockchain. After the spendbundle succeeds, all of the relevant coins have been spent or created, and all assertions have been completed. At this point, the offer's status is CONFIRMED.


Market makers#

Offers in the PENDING_CONFIRM state have been added to the mempool. Farmers and third-party software can observe the current list of offers, and aggregate overlapping ones. This operation is known as a "market maker."

Automated Market Makers (AMMs) are likely to appear in Chia's ecosystem. AMMs can create a single settlement puzzle for each type of asset (XCH or a specific type of CAT), and aggregate all of the notarized coin payments of that type in the puzzle.

A "Taker" is part offer-creator, part market-maker. A Taker finds an offer of interest and constructs the other side of that offer, using both of the settlement_payments puzzles to resolve the cross-asset payments.

Offer aggregation#

A sophisticated AMM might aggregate multiple settlement_payments into a single spend, which means it could combine an arbitrary number of offers, paying through one settlement_payments ephemeral coin for each asset type.

For example, a whale wants to buy 10,000 XCH, and is currently sitting on a large stack of stablecoins. There aren't any individuals willing to sell such a large amount of XCH, but the whale doesn't need to create a bunch of small offers. Instead, they create a single offer: X stablecoins (at the current market price) for 10,000 XCH. Several small XCH holders can aggregate their holdings to complete the offer.

Oracles and arbitrage#

As discussed previously, an offer in the PENDING_ACCEPT state cannot be modified. If the price of any asset changes after the offer has been created, AMMs could take advantage of arbitrage by accepting stale offers and creating new ones. However, this also means that farmers could do the same, taking the arbitrage for themselves.

For example:

  1. Alice offers 1 XCH for 251 CKC.
  2. The spot price of XCH doubles to 502 CKC per XCH.
  3. An AMM notices the price increase and decides to accept the offer. They could then sell the 1 XCH and profit 251 CKC, which would be worth 0.5 XCH.
  4. However, Farmer Bob notices the AMM's attempt at arbitrage and also accepts the offer, adding his own acceptance (and not the AMM's) to the block. Bob has effectively taken the AMM's intended arbitrage.
  5. The AMM's only option to taking the offer is to add a fee of at least 0.5 XCH, which would cost them more money than they stood to make in the first place. Bob has secured all of the financial incentive for himself.

Additionally, price oracles could be used to reduce MEV, ensure fairness, and stabilize the offers markets. There are many potential business opportunities in this realm, which would have to be developed external to Chia Network Inc.


Offer file quirks#

A few aspects of offer files might seem counter-intuitive at first.

On-chain vs off-chain#

When a Maker creates an offer, it's moved to the PENDING_ACCEPT state. So far, nothing has been written to the blockchain. In fact, nobody else will even know of the offer's existence until the Maker shares the file. If the Maker changes their mind before sending the offer file to anyone else, the Maker can delete the file, and the offer will have been effectively "canceled."

However, the Maker's wallet needs to keep track of any pending offers, in case the Maker decides to initiate any additional transactions before the offer has been accepted.

For example, let's say Alice has 1 XCH and wants to buy 251 CKC (thereby spending all of her XCH). When she creates her offer, her wallet will store the details locally in its database. All of her XCH coins (totaling 1 XCH) have been reserved for the offer, so she can't spend them while the offer is still pending.

If Alice's 1 XCH is only reserved locally, what's stopping her from using a third-party wallet to spend that money? Nothing! The third-party wallet will ascertain its balance by examining the blockchain, which does not know about the pending offer. Therefore, the third-party wallet hasn't reserved any coins for the offer. Alice absolutely could still send someone else her XCH, but then the offer would no longer be valid.

If Alice wants to be sure all of her wallets are in agreement, she must cancel her offer.

Cancellation#

A Maker has two options to cancel an offer.

  1. Cancel on the blockchain. This is the default cancellation option. It should be used if the Maker has sent the offer file to anyone else.

    Continuing our previous example, if Alice uses this option, then her wallet will spend the coins being offered, and create new coins of the same type and value. Alice doesn't need to know about the underlying coins that have been spent -- she just sees her old balance return to her wallet. The offer is now complete and can't be used again.

    This option does come with two small downsides.

    • Alice must wait for the blockchain to confirm that the cancellation transaction has completed.
    • Alice might have to pay a transaction fee.

    These downsides will likely be acceptable in most cases, so "cancel on the blockchain" is the default option.

  2. Cancel locally only. This option should only be used if the Maker has not sent the offer file to anyone else.

    This option will simply notify Alice's wallet that the transaction has been canceled, so the coins will no longer be reserved. There is no blockchain transaction, so the two disadvantages of "Cancel on the blockchain" don't apply here -- the cancellation happens instantly and there is no need for a transaction fee. The downside of this option is that if the offer file ever leaves Alice's computer, a Taker can still take the offer (as long as Alice's coins have not been spent).

    NOTE: Double spends on the blockchain aren't possible for either of these options. In the coin set model, coins can only ever be spent once. If someone attempts to take Alice's offer at the same time she spends the coins, only one of those transactions will make it onto the blockchain. Chialisp.com has a detailed explanation of the coin set model.

Coin set (UTXO)#

Chia uses the coin set model to keep track of state. This is similar to the UTXO model in Bitcoin. The coin set model has many advantages over the account model, but it can create some situations that take time to understand.

In the coin set model, everything is a coin. There are no accounts, at least not at the blockchain level. Coins can only ever be spent once, and they can't be divided.

This is similar to how cash works. If Alice has a $20 bill and she wants to buy something for $1, she can't just rip out a piece of the bill and hand it to the cashier. She has to pay with the whole bill and wait for her change.

Chia works the same way. If someone sends Alice 20 XCH, her wallet is now tracking a single coin worth 20 XCH. If she makes an offer for 1 XCH, her wallet needs to reserve the entire coin for the offer. While the offer is pending, Alice can't spend any of her coin.

However, Alice can get around this issue by sending money to herself. Let's say she sends 10 XCH to her own wallet. Her 20-XCH coin has been spent and two new coins have been created, both worth 10 XCH. She can now make an offer for 1 XCH, thus reserving one of her coins, and she can spend the other coin in the usual manner while the offer is pending.

The GUI tutorial goes over an example of this process.


CLI Usage#

Chia's command line interface provides a set of commands to make, take, cancel, and list offers. To use offers on the command line, make sure you are using a virtual environment.

The relevant commands can all be found under the chia wallet command:

(venv) $ chia wallet -h

Commands#

Reference#

make_offer#

Functionality: Create an offer of XCH/CATs for XCH/CATs.

Usage: chia wallet make_offer [OPTIONS]

Options:

Short CommandLong CommandTypeRequiredDescription
-wp--wallet-rpc-portINTEGERFalseSet the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml
-f--fingerprintINTEGERFalseSet the fingerprint to specify which wallet to use
-o--offerTEXTTrueA wallet id to offer and the amount to offer (formatted like wallet_id:amount)
-r--requestTEXTTrueA wallet id of an asset to receive and the amount you wish to receive (formatted like wallet_id:amount)
-p--filepathTEXTTrueThe path to write the generated offer file to
-m--feeTEXTFalseA fee to add to the offer when it gets taken
-h--helpNoneFalseShow a help message and exit

take_offer#

Functionality: Examine or take an offer.

Usage: chia wallet take_offer [OPTIONS] PATH_OR_HEX

Options:

Short CommandLong CommandTypeRequiredDescription
-wp--wallet-rpc-portINTEGERFalseSet the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml
-f--fingerprintINTEGERFalseSet the fingerprint to specify which wallet to use
-e--examine-onlyNoneFalsePrint the summary of the offer file but do not take it
-m--feeTEXTFalseThe fee to use when pushing the completed offer
-h--helpNoneFalseShow a help message and exit

cancel_offer#

Functionality: Cancel an existing offer. Must be the offer's Maker.

Usage: chia wallet cancel_offer [OPTIONS]

Options:

Short CommandLong CommandTypeRequiredDescription
-wp--wallet-rpc-portINTEGERFalseSet the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml
-f--fingerprintINTEGERFalseSet the fingerprint to specify which wallet to use
-id--idTEXTTrueThe offer ID that you wish to cancel
N/A--insecureNoneFalseDon't make an on-chain transaction, simply mark the offer as canceled
-m--feeTEXTFalseThe fee to use when canceling the offer securely
-h--helpNoneFalseShow a help message and exit

get_offers#

Functionality: Get the status of existing offers.

Usage: chia wallet get_offers [OPTIONS]

Options:

Short CommandLong CommandTypeRequiredDescription
-wp--wallet-rpc-portINTEGERFalseSet the port where the Wallet is hosting the RPC interface. See the rpc_port under wallet in config.yaml
-f--fingerprintINTEGERFalseSet the fingerprint to specify which wallet to use
-id--idTEXTFalseThe ID of the offer that you wish to examine
-p--filepathTEXTFalseThe path to rewrite the offer file to (must be used in conjunction with --id)
-ia--include-allNoneFalseInclude offers that have already been confirmed/canceled
-s--summariesNoneFalseShow the assets being offered and requested for each offer
-h--helpNoneFalseShow a help message and exit

CLI examples#

For detailed examples of offers using the command line interface, see our CLI tutorial.


RPCs#

From wallet_rpc_client.py:

create_offer_for_ids#

Creates a new offer.

  • offer_dict: A dictionary of the offer to create.
  • fee: An optional fee to include with the offer. Defaults to 0.
  • validate_only: Defaults to False. Set to True to verify the validity of a potential offer, rather than actually creating an offer.
async def create_offer_for_ids(  self, offer_dict: Dict[uint32, int], fee=uint64(0), validate_only: bool = False) -> Tuple[Optional[Offer], TradeRecord]:  send_dict: Dict[str, int] = {}  for key in offer_dict:    send_dict[str(key)] = offer_dict[key]
  res = await self.fetch("create_offer_for_ids", {"offer": send_dict, "validate_only": validate_only, "fee": fee})  offer: Optional[Offer] = None if validate_only else Offer.from_bytes(hexstr_to_bytes(res["offer"]))  return offer, TradeRecord.from_json_dict_convenience(res["trade_record"], res["offer"])

get_offer_summary#

Returns the summary of a specific offer. Works for offers in any state.

  • offer: The offer to summarize.
async def get_offer_summary(self, offer: Offer) -> Dict[str, Dict[str, int]]:  res = await self.fetch("get_offer_summary", {"offer": bytes(offer).hex()})  return res["summary"]

check_offer_validity#

Checks whether a specific offer is valid.

  • offer: The offer to check. The offer is considered valid if it is in any of the following states:

    • PENDING_ACCEPT
    • PENDING_CONFIRM
    • PENDING_CANCEL

    The offer is no longer valid if it is in any of the following states:

    • CANCELLED
    • CONFIRMED
    • FAILED
async def check_offer_validity(self, offer: Offer) -> bool:  res = await self.fetch("check_offer_validity", {"offer": bytes(offer).hex()})  return res["valid"]

take_offer#

Takes (accepts) a specific offer, with a given fee.

  • offer: The offer to accept. Must be in the PENDING_ACCEPT state.
  • fee: An optional fee to include with the offer. Defaults to 0.
async def take_offer(self, offer: Offer, fee=uint64(0)) -> TradeRecord:  res = await self.fetch("take_offer", {"offer": bytes(offer).hex(), "fee": fee})  return TradeRecord.from_json_dict_convenience(res["trade_record"])

get_offer#

Given an offer's unique identifier, return that offer's details.

  • trade_id: The ID of the offer to examine. Can be retrieved from an offer file by calling cdv inspect spendbundles <offer_file>.
  • file_contents: Set to True to return a summary for the offer. Defaults to False, which only returns the offer's basic metadata.
async def get_offer(self, trade_id: bytes32, file_contents: bool = False) -> TradeRecord:  res = await self.fetch("get_offer", {"trade_id": trade_id.hex(), "file_contents": file_contents})  offer_str = res["offer"] if file_contents else ""  return TradeRecord.from_json_dict_convenience(res["trade_record"], offer_str)

get_all_offers#

Gets all offers for the current wallet. Includes offers in every state.

  • file_contents Set to True to return a summary for the offer. Defaults to False, which only returns the offer's basic metadata.
async def get_all_offers(self, file_contents: bool = False) -> List[TradeRecord]:  res = await self.fetch("get_all_offers", {"file_contents": file_contents})
  records = []  optional_offers = res["offers"] if file_contents else ([""] * len(res["trade_records"]))  for record, offer in zip(res["trade_records"], optional_offers):    records.append(TradeRecord.from_json_dict_convenience(record, offer))
  return records

cancel_offer#

Cancel an offer with a specific identifier.

  • trade_id: The ID of the offer to examine. Can be retrieved from an offer file by calling cdv inspect spendbundles <offer_file>.
  • fee: An optional fee to include with the cancellation. Defaults to 0.
  • secure: Defaults to True, which means "cancel on blockchain," ie spend the coins being offered and create new coin's in the Maker's wallet. Set to False to cancel locally. See cancellation for more info.
async def cancel_offer(self, trade_id: bytes32, fee=uint64(0), secure: bool = True):  await self.fetch("cancel_offer", {"trade_id": trade_id.hex(), "secure": secure, "fee": fee})

Glossary of terms#

  • AMM -- Automated Market Maker, a software protocol that uses liquidity pools to enable the permissionless trading of digital assets.
  • Asset -- In the context of offers, an asset may refer to XCH, CATs, or even NFTs that live on Chia's blockchain.
  • Liquidity Pool -- A shared pot of a digital asset's coins or tokens. AMMs use these in lieu of middlemen to connect buyers and sellers.
  • Maker -- The party (human or computer) who creates the offer.
  • Nonce -- A Number ONly used onCE, typically applied to prevent replay attacks. See Wikipedia for more info.
  • Notarized coin -- A coin the Maker wishes to receive upon an offer's acceptance.
  • notarized_payments -- A list of notarized coins.
  • Offer -- A bid to exchange assets on Chia's ecosystem in a peer-to-peer manner.
  • Offer File -- A text file containing the details of an offer, stored in hexadecimal bytecode. Offer files don't contain any private keys, so they may be shared publicly.
  • Price oracle -- An automated software program that provides off-chain data to a blockchain's ecosystem.
  • Taker -- The party (human or computer) who accepts the offer. Multiple Takers can be aggregated to complete an offer.
  • settlement_payments -- A Chialisp puzzle used in offers. It takes notarized_payments as its solution, and creates a coin and a puzzle announcement for each notarized coin.