Skip to main content

APIs

RichSwap is the first AMM DEX exchange on REE.

For Rich Swap testnet4 please visit Rich Swap Testnet.

For Rich Swap support please visit The Rich Swap Channel in both English and Chinese.

Canister Id
Rich Swapkmwen-yaaaa-aaaar-qam3a-cai
Rich Swap Testneth43eb-lqaaa-aaaao-qjxgq-cai

Query

Workflow(e.g., Swap):

The core business logic of Rich Swap is encapsulated within the following functions: pre_withdraw_liquidity, pre_add_liquidity, pre_swap, pre_donate(to ensure fairness and incentivize long-term liquidity provision) and create. To gain a clearer understanding of their functionality, please refer to the example workflow provided below:

1. Invoke pre_swap and Display SwapOffer:

The client application call the pre_swap function based on user input. Once the SwapOffer is generated, it should be displayed on the frontend for the user to review and confirm. For every update request that modifies the pool's state, there is a corresponding pre_x method. In the case of a swap, the pre_swap method is invoked to retrieve the quotation and the pool's utxo as input.

2. Construct a PSBT Using the Wallet API:

After the user confirms the SwapOffer, the frontend will use the wallet api to construct a psbt (Partially Signed Bitcoin Transaction) based on the provided input parameters and the estimate_min_tx_fee, following the verification of the address's utxos. Unlike BTC utxos, which can be fetched with 0 confirmations through the Unisat api, Runes utxos must be retrieved using the get_zero_confirmed_utxos_of_address method from REE. This step involves significant technical complexity. For a comprehensive guide on constructing a psbt, refer to this example.

3. Invoke REE's invoke Function:

Once the PSBT is constructed, it is passed along with the intention(see the example) to call REE's invoke function. This function will subsequently trigger the execution_tx function of the Rich Swap exchange. The Rich Swap exchange will then perform the necessary checks to ensure the transaction is valid.

4. Broadcast the Transaction and Handle Results:

When all transactions processed through the exchange are successful, REE will finalize and broadcast them, returning the result information indicating that the transaction was processed successfully. If the transaction fails, the Mempool Connector will notify REE, and an error will be returned instead.

pre_withdraw_liquidity

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

type WithdrawalOffer = record {
nonce : nat64;
input : Utxo;
user_outputs : vec CoinBalance;
};

type ExchangeError = variant {
InvalidSignPsbtArgs : text;
UtxoMismatch;
InvalidNumeric;
Overflow;
InvalidInput;
PoolAddressNotFound;
RuneIndexerError : text;
PoolStateExpired : nat64;
TooSmallFunds;
InvalidRuneId;
InvalidPool;
InvalidPsbt : text;
PoolAlreadyExists;
InvalidTxid;
InvalidLiquidity;
EmptyPool;
FetchBitcoinCanisterError;
LpNotFound;
NoConfirmedUtxos;
ChainKeyError;
FetchRuneIndexerError;
InvalidState : text;
InsufficientFunds;
};

type Result_11 = variant { Ok : WithdrawalOffer; Err : ExchangeError };

pre_withdraw_liquidity : (text, text, nat) -> (Result_11) query;
  • Input: pool_addr - String (pool address)

  • Input: user_addr - String

  • Input: share - u128

  • WithdrawalOffer: for constructing the PSBT as part of the inputs

pre_add_liquidity

type ExchangeError = variant {
InvalidSignPsbtArgs : text;
UtxoMismatch;
InvalidNumeric;
Overflow;
InvalidInput;
PoolAddressNotFound;
RuneIndexerError : text;
PoolStateExpired : nat64;
TooSmallFunds;
InvalidRuneId;
InvalidPool;
InvalidPsbt : text;
PoolAlreadyExists;
InvalidTxid;
InvalidLiquidity;
EmptyPool;
FetchBitcoinCanisterError;
LpNotFound;
NoConfirmedUtxos;
ChainKeyError;
FetchRuneIndexerError;
InvalidState : text;
InsufficientFunds;
};

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

type LiquidityOffer = record {
output : CoinBalance;
inputs : opt Utxo;
nonce : nat64;
};

type Result_6 = variant { Ok : LiquidityOffer; Err : ExchangeError };

pre_add_liquidity : (text, CoinBalance) -> (Result_6) query;
  • Input: pool_addr - String (pool address)

  • Input: side - CoinBalance (user's input)

  • LiquidityOffer: for constructing the PSBT as part of the inputs

pre_swap

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

type Utxo = record {
maybe_rune : opt CoinBalance;
sats : nat64;
txid : text;
vout : nat32;
};

type SwapOffer = record { output : CoinBalance; nonce : nat64; input : Utxo };

type ExchangeError = variant {
InvalidSignPsbtArgs : text;
UtxoMismatch;
InvalidNumeric;
Overflow;
InvalidInput;
PoolAddressNotFound;
RuneIndexerError : text;
PoolStateExpired : nat64;
TooSmallFunds;
InvalidRuneId;
InvalidPool;
InvalidPsbt : text;
PoolAlreadyExists;
InvalidTxid;
InvalidLiquidity;
EmptyPool;
FetchBitcoinCanisterError;
LpNotFound;
NoConfirmedUtxos;
ChainKeyError;
FetchRuneIndexerError;
InvalidState : text;
InsufficientFunds;
};

type Result_9 = variant { Ok : SwapOffer; Err : ExchangeError };

pre_swap : (text, CoinBalance) -> (Result_9) query;
  • Input: id - String (pool address)

  • Input: input - CoinBalance (user's input)

  • SwapOffer: for constructing the PSBT as part of the inputs

pre_donate

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

type Utxo = record {
maybe_rune : opt CoinBalance;
sats : nat64;
txid : text;
vout : nat32;
};

type DonateIntention = record {
out_rune : CoinBalance;
out_sats : nat64;
nonce : nat64;
input : Utxo;
};

type ExchangeError = variant {
InvalidSignPsbtArgs : text;
UtxoMismatch;
InvalidNumeric;
Overflow;
InvalidInput;
PoolAddressNotFound;
RuneIndexerError : text;
PoolStateExpired : nat64;
TooSmallFunds;
InvalidRuneId;
InvalidPool;
InvalidPsbt : text;
PoolAlreadyExists;
InvalidTxid;
InvalidLiquidity;
EmptyPool;
FetchBitcoinCanisterError;
LpNotFound;
NoConfirmedUtxos;
ChainKeyError;
FetchRuneIndexerError;
InvalidState : text;
InsufficientFunds;
};

type Result_7 = variant { Ok : DonateIntention; Err : ExchangeError };

pre_donate : (text, nat64) -> (Result_7) query;
  • Input: pool - String (pool address)
  • Input: input_sats - u64 (in satoshi)

And it returns: DonateIntention: for constructing the PSBT as part of the inputs

  • out_rune - the rune output to pool
  • out_sats - the btc output to pool
  • nonce - pool transaction indexing
  • input - the utxo belongs to pool

Donation Workflow:

The donation process follows a similar workflow to the example above:

For each DonateIntention, the parameters are determined by business logic. In this case (action = "donate"), the exchange enforces the following rules:

  • Exactly 1 input_coins, which must be BTC (ID: "0:0")
  • No output_coins
  • pool_utxo_spend must reference the UTXO of the just-received DonateIntention
  • pool_utxo_receive must reference this transaction’s UTXO

PSBT Structure Inputs:

  • Input 0: DonateIntention::input
  • Input 1: User’s input (BTC to donate) Outputs:
  • Output 0: DonateIntention::out_sats (donation amount)
  • Output 1: Encoded OP_RETURN(out_rune)
  • Output 2: User’s change (if applicable)
    IntentionSet {
tx_fee_in_sats: fee,
initiator_address: input_address.to_string(),
intentions: vec![Intention {
input_coins: vec![InputCoin {
coin: CoinBalance {
id: "0:0".to_string(),
value: 10000,
},
from: input_address.to_string(),
}],
output_coins: Vec::new(),
action: "donate".to_string(),
exchange_id: "RICH_SWAP".to_string(),
pool_utxo_spend: vec!["{txid}:{vout}"],
action_params: "".to_string(),
nonce,
pool_utxo_receive: vec!["{txid}:{vout}"],
pool_address: pool_address.to_string(),
}],
};

Please see the code here for details (this simple CLI tool donates 10,000 sats to a specified pool):

To run it:

./target/debug/donate-cli \
--pool-address tb1puzk3gn4z3rrjjnrhlgk5yvts8ejs0j2umazpcc4anq62wfk00e6ssw9p0n \
--input-priv-key {your_private_key} \
--rpc-url $BTC_RPC_URL \
--network testnet

The functions get_pool_list, get_pool_info, and get_minimal_tx_value are required for REE in the standard query api.

For more details, please refer to the Exchange Interfaces documentation.

get_pool_list

type PoolBasic = record { name : text; address : text };

get_pool_list : () -> (vec PoolBasic) query;

Retrieved all the pool information in the exchange.

  • Output: name - String (pool name)
  • Output: address - String (pool address)

For those who need to fetch the pool address via the runes name, please refer to the code example here:

Typescript
import { gql } from 'graphql-request'
import { request } from 'graphql-request'

const HOST =
NETWORK === 'Mainnet'
? 'https://ree-hasura-mainnet.omnity.network/v1/graphql'
: 'https://ree-hasura-testnet.omnity.network/v1/graphql'

export const fetchGraphFromRee = async (query: string, variables: any) => {
try {
const data = await request({
url: HOST,
document: query,
variables,
})
return data as any
} catch (error) {
console.error(error)
return null
}
}

const poolDoc = gql`
{
pool_info(where: {name: {_eq: "HOPE•YOU•GET•RICH"}})
{
address
}
}
`

const poolResult = await fetchGraphFromRee(poolDoc, {})

get_pool_info

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

type Utxo = record {
maybe_rune : opt CoinBalance;
sats : nat64;
txid : text;
vout : nat32;
};

type PoolInfo = record {
key : text;
name : text;
btc_reserved : nat64;
key_derivation_path : vec blob;
coin_reserved : vec CoinBalance;
attributes : text;
address : text;
nonce : nat64;
utxos : vec Utxo;
};

type GetPoolInfoArgs = record { pool_address : text };

get_pool_info : (GetPoolInfoArgs) -> (opt PoolInfo) query;

Get the META information of a certain pool.

  • key : Retrieve all pool information within the exchange, where the key is the untweaked public key of a Pay-to-Taproot (P2TR) address, ensuring that the spending conditions of the pool's utxos can be verified.
  • name: e.g.,:HOPE•YOU•GET•RICH
  • btc_reserved : e.g.,:103845689
  • key_derivation_path: e.g,: [ [ 0, 0, 0, 0, 0, 12, 209, 64, 0, 0, 3, 78 ] ]
  • coin_reserved : e.g.,:id="840000:846"; value=54286490476
  • attributes : e.g.,: "fee_rate":7000,"burn_rate":2000,"tweaked":"5ccc93f7bf8f43941c7511203d595bbf5a83c630ca9bbace10ff9a397c0dbaa4","incomes":472860,"sqrt_k":2355907027
  • address e.g.,:bc1ptnxf8aal3apeg8r4zysr6k2mhadg833se2dm4nssl7drjlqdh2jqa4tk3p
  • nonce : e.g.,:594
  • utxos : e.g.,:vec {record {maybe_rune=opt record {id="840000:846"; value=54286490476}; sats=104318549; txid="62f607dedfb5b77b7f09cff901cc52f846306190377afc6f910d09e5b5f239a4"; vout=0}}}

get_minimal_tx_value

type GetMinimalTxValueArgs = record {
zero_confirmed_tx_queue_length : nat32;
pool_address : text;
};

get_minimal_tx_value : (GetMinimalTxValueArgs) -> (nat64) query;

Retrieve the minimum transaction value allowed by Rich Swap (Hardcoded as a temporary solution).

get_lp

type Liquidity = record {
total_share : nat;
user_share : nat;
user_incomes : nat64;
};

type ExchangeError = variant {
InvalidSignPsbtArgs : text;
UtxoMismatch;
InvalidNumeric;
Overflow;
InvalidInput;
PoolAddressNotFound;
RuneIndexerError : text;
PoolStateExpired : nat64;
TooSmallFunds;
InvalidRuneId;
InvalidPool;
InvalidPsbt : text;
PoolAlreadyExists;
InvalidTxid;
InvalidLiquidity;
EmptyPool;
FetchBitcoinCanisterError;
LpNotFound;
NoConfirmedUtxos;
ChainKeyError;
FetchRuneIndexerError;
InvalidState : text;
InsufficientFunds;
};

type Result_4 = variant { Ok : Liquidity; Err : ExchangeError };

get_lp : (text, text) -> (Result_4) query;

Query the user's liquidity share ratio. It specifies the following parameters:

  • pool_addr - String: e.g.,: 5c9eaaf2e8821d8810c625f5039ed69db13f3e6fb2ed4f3c9194e212bfc88428
  • user_addr - String

And it returns:

  • user_share : it represents the user's share. Although RichSwap does not use an LP token mechanism, this value is effectively equivalent to the amount of LP tokens
  • sqrt_k : btc_withdraw * rune_withdraw
  • btc_supply

Update

create

type ExchangeError = variant {
InvalidSignPsbtArgs : text;
UtxoMismatch;
InvalidNumeric;
Overflow;
InvalidInput;
PoolAddressNotFound;
RuneIndexerError : text;
PoolStateExpired : nat64;
TooSmallFunds;
InvalidRuneId;
InvalidPool;
InvalidPsbt : text;
PoolAlreadyExists;
InvalidTxid;
InvalidLiquidity;
EmptyPool;
FetchBitcoinCanisterError;
LpNotFound;
NoConfirmedUtxos;
ChainKeyError;
FetchRuneIndexerError;
InvalidState : text;
InsufficientFunds;
};

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

create : (text) -> (Result_1);

Pool creation is limited to BTC paired exclusively with a RUNE.

  • Input: rune_id - e.g.,:840000:846
  • Output: Pubkey - e.g.,: 5c9eaaf2e8821d8810c625f5039ed69db13f3e6fb2ed4f3c9194e212bfc88428

Last updated on May 9, 2025