Skip to main content

APIs

The Runes Exchange Environment (REE) is a Turing-complete, bridgeless execution layer for BTCFi. The REE Orchestrator is responsible for managing the execution of exchange programs within REE. Please explore How REE Works Under The Hood and review its white paper.

From the backend perspective, REE is composed of four key components:

  • REE Orchestrator: Commonly referred to as REE, this core canister is responsible for managing the execution of exchange programs within the REE platform.
  • Runes Indexer: A canister that indexes runes and provides information about new blocks.
  • Mempool Connector: A daemon that updates the fee rate and monitors rejected transactions from REE on the Mempool, notifies the REE Orchestrator.
  • Exchanges: Instances of the BTCFi protocol operating on the REE platform, designed to facilitate coin exchange. Each exchange must adhere to the formal api principles established by REE. Please refer to the REE Dev Guide for further details. There are multiple exchanges, and each exchange consists of several pools. REE manages the state of each exchange on a per-pool basis.

And the helper components:

  • REE Exchange Registry: Please be advised that the new exchange requires manual registration (by providing the exchange ID and canister ID) before it can call invoke. The exchange canister must be deployed on the same subnet as the orchestrator, which is also the same subnet as the chain key. Otherwise, the speed will be significantly slower.
  • UTXO Proof Server: A lightweight HTTP server built with Tokio and Axum that generates CBOR‑serialized UTXO proofs for Bitcoin addresses and returns them as JSON.
  • REE Types: The essential data type definitions for REE. It is recommended to use it for the Exchange development.

For Ree support please visit The REE Dev Support Channel in English and The REE Dev Support CN Channel in Chinese.

Canister Id
Orchestratorkqs64-paaaa-aaaar-qamza-cai
Orchestrator Testnethvyp5-5yaaa-aaaao-qjxha-cai

Update

invoke

type CoinBalance = record { id : text; value : nat };

type InputCoin = record { coin : CoinBalance; from : text };

type OutputCoin = record { to : text; coin : CoinBalance };

type Utxo = record {
coins : vec CoinBalance;
sats : nat64;
txid : text;
vout : nat32;
};

type Intention = record {
input_coins : vec InputCoin;
output_coins : vec OutputCoin;
action : text;
exchange_id : text;
pool_utxo_spent : vec text;
action_params : text;
nonce : nat64;
pool_address : text;
pool_utxo_received : vec Utxo;
};

type IntentionSet = record {
tx_fee_in_sats : nat64;
initiator_address : text;
intentions : vec Intention;
};

type InvokeArgs = record {
intention_set : IntentionSet;
initiator_utxo_proof : blob;
psbt_hex : text;
};


type Result_3 = variant { Ok : text; Err : text };

invoke : (InvokeArgs) -> (Result_3);

See the instruction examples or the code here for details (this simple CLI tool donates 10,000 sats to a specified pool).

For the initiator_utxo_proof, please pass an empty string. If the client provides a proof, the Orchestrator will inspect and verify it, rejecting the request if verification fails. If the proof is empty or verification succeeds, the request is allowed. For calls with an empty proof, the Orchestrator performs verification via the Bitcoin canister.

Check the REE Orchestrator InvokeStatus Code List for error codes.

The core business function of the orchestrator. It processes exchange execution requests. Review all the checks that are performed when the invoke function is called.

# Checks for 'Invoke' in REE

This document summarizes the checks performed by the REE orchestrator when a client performs an `invoke`.

The `invoke` function of the REE Orchestrator accepts the following parameters:

- **`psbt_hex`**: The PSBT data in hexadecimal format.
- **`intention_set`**: The client's transaction intentions to apply in REE. It is defined as:

```rust
pub struct InputCoin {
// The address of the owner of the coins
pub from: String,
pub coin: CoinBalance,
}

pub struct OutputCoin {
// The address of the receiver of the coins
pub to: String,
pub coin: CoinBalance,
}

pub struct Intention {
pub exchange_id: String,
pub action: String,
pub pool_address: String,
pub nonce: u64,
pub pool_utxo_spend: Vec<String>,
pub pool_utxo_receive: Vec<String>,
pub input_coins: Vec<InputCoin>,
pub output_coins: Vec<OutputCoin>,
}

pub struct IntentionSet {
pub initiator_address: String,
pub tx_fee_in_sats: u64,
pub intentions: Vec<Intention>,
}


**In general, the REE Orchestrator will ensure that the intention set EXACTLY matches the PSBT.**

Checks for the PSBT

The REE Orchestrator will check the following facts for the input PSBT:

- The input PSBT is a valid PSBT object.
- At least one signed input belongs to the `initiator_address` in the intention set.
- The tx fee of the final Bitcoin transaction is exactly equal to the `tx_fee_in_sats` in the intention set.
- The UTXOs used as inputs in the PSBT can only be P2SH, P2WPKH, or P2TR.
- All UTXOs used as inputs in the PSBT are all valid: **(In Development)**
- They have at least one confirmation.
- They are not spent in any other confirmed transaction or any other unconfirmed transaction sent from the REE Orchestrator.
- For rune asset, a correctly formatted `OP_RETURN` output must be included in the PSBT. (The receivers of runes must be specified explicitly.) An empty `OP_RETURN` output or missing `OP_RETURN` output is not allowed.
- The final Bitcoin transaction extracted from the PSBT, after all intentions are executed in corresponding exchanges, is a valid transaction. Including:
- All inputs are signed correctly, and the final witness script is valid.
- `total amount of BTC in all inputs` = `total amount of BTC in all outputs` + `tx_fee_in_sats`.
- For each rune asset in the transaction: `total amount of rune in all inputs` = `total amount of rune in all outputs`.

Checks for the Intention Set

The REE Orchestrator will check the following facts for the intention set:

- The `exchange_id` of each intention is valid (must be one of the registered exchanges).
- The `action` of each intention is valid:
- The length of the action name must be `> 1` and `<= 64`.
- The action name must be a valid function name in Rust. (It matches the regex `^[a-zA-Z_][a-zA-Z0-9_]*$`).
- The `utxo_spend` of each intention is valid:
- If it is specified, it must be a valid outpoint (formatted as `<txid>:<vout>`) used in the PSBT.
- The `utxo_receive` of each intention is valid:
- If it is specified, it must be a valid outpoint (formatted as `<txid>:<vout>`) of the `unsigned_tx` in the PSBT.
- The `from` address of each `input_coin` must NOT be the `pool_address` of the intention.
- The `to` address of each `output_coin` must exist in the outputs of the PSBT.
- The total amount of all assets of non-pool addresses corresponding to this invoke is correctly set in PSBT:
- For BTC asset: `the total amount of BTC in all inputs of the PSBT from addresses other than the pool addresses` - `the total amount of BTC in all input_coins` - `tx_fee_in_sats` + `the total amount of BTC in all output_coins` == `the total amount of BTC in all outputs of the PSBT to addresses other than the pool addresses.
- For rune asset: `the total amount of the given rune in all inputs of the PSBT from addresses other than the pool addresses` - `the total amount of the given rune in all input_coins` + `the total amount of the given rune in all output_coins` == `the total amount of the given rune in all outputs of the PSBT to addresses other than the pool addresses.

Checks for the Intention Execution

Before executing each intention in the corresponding exchange, the REE Orchestrator will check the following facts:

- If the `input_coins` of the current intention is not empty, for each `input_coin`, the total amount of the given asset in all inputs of the PSBT must not be less than the balance specified in the corresponding `input_coin`, and they are all signed correctly. (The asset can come from the addresses other than the `pool_address` of the current intention.)

After executing each intention in the corresponding exchange, the REE Orchestrator will check the following facts:

- The transaction ID of the final transaction extracted from the returned PSBT must NOT be changed.

Query

get_zero_confirmed_utxos_of_address

type CoinBalance = record { id : text; value : nat };

type OutpointWithValue = record {
maybe_rune : opt CoinBalance;
value : nat64;
script_pubkey_hex : text;
outpoint : text;
};

get_zero_confirmed_utxos_of_address : (text) -> (vec OutpointWithValue) query;

Retrieve UTXOs with zero confirmed runes for a specific address. The following address input formats are supported:

  • Pay to witness public key hash (P2WPKH)

  • Pay to taproot (P2TR)

If the address is malformed, the call is rejected. This endpoint returns the Outpoint of the address. It specifies the following parameters:

  • maybe_rune: e.g.,:840000:846
  • value
  • script_pubkey_hex: e.g.,:00142442018f491970523d89837488eabba965f2c785
  • outpoint: txid:vout - e.g.,:9870d98efd36f4bed923205b52c06e9d4d0b1613031984872ade966f2646eba2:0

estimate_min_tx_fee

type TxOutputType = variant { P2WPKH; OpReturn : nat64; P2SH; P2TR };

type EstimateMinTxFeeArgs = record {
input_types : vec TxOutputType;
pool_address : vec text;
output_types : vec TxOutputType;
};

type Result_1 = variant { Ok : nat64; Err : text };

estimate_min_tx_fee : (EstimateMinTxFeeArgs) -> (Result_1) query;

It specifies the following parameters:

  • input_types : e.g.,:vec![TxOutputType::P2WPKH, TxOutputType::P2TR],
  • pool_address : e.g.,:bc1ptnxf8aal3apeg8r4zysr6k2mhadg833se2dm4nssl7drjlqdh2jqa4tk3p
  • output_types : e.g.,:vec![ TxOutputType::P2TR, TxOutputType::P2WPKH, TxOutputType::P2WPKH, TxOutputType::OpReturn(14), ]

Notes:
The TxOutputType::OpReturn(Nat) should be set by the actual bytes count of OP_RETRUN output rather than hardcoded 0. If you use 0 here and you use a non-empty OP_RETURN in PSBT of InvokeArgs, it will cause the 301 error.

You should calculate the length of the OP_RETURN output before calling estimate_min_tx_fee function. For example, if the length of your actual OP_RETURN output is 17, then you should call estimate_min_tx_fee with

#OpReturn(17: Nat64),

In previous version, there is no OP_RETURN output in your PSBT of InvokeArgs. Only in this case, the estimated min tx fee is right if you pass the params like these:

#OpReturn(0: Nat64),

Last updated on June 27, 2025