# Overview

radFi is an **Automated Market Maker (AMM)** protocol for Bitcoin and runes that executes trades entirely on the **Bitcoin** mainnet. This enables two primary activities for Bitcoin users:

1. **Automated Market Making**: Users can earn yield on their BTC and runes by trading within a customizable range. By depositing assets into radFi pools, users can execute their chosen strategy. These users are known as Liquidity Providers (LPs).
2. **Instant Swaps**: Users can buy or sell runes with near-instant execution, multiple times within a single block. Settlement occurs at the end of the block. These users are referred to as “degenerate traders.”

radFi was inspired by the concept of **Light Pools**, originally authored by **Casey Rodarmor**.

“*The idea behind light pools is simple. Users who wish to offer swaps between Bitcoin-native assets, like rare sats, inscriptions, or runes, run nodes that quote prices for swaps.*”

radFi operates as described, running a node that quotes prices for swaps between Bitcoin-native assets. While Casey critiques the AMM structure for its lack of technical efficiency, we believe that combining the concept of **Light Pools with AMM-style pricing and automation** allows less technical users to participate, ultimately deepening liquidity for Bitcoin tokens.


# Economics

### Trading Fees

radFi generates revenue from fees charged on trading volume. Liquidity Providers can choose from four fee tiers: **0.05%**, **0.30%**, **0.50%**, and **1.00%**. The revenue is then distributed as follows:

* **Liquidity Providers: 2/3**
* **radFi: 1/3**

This fee structure is designed to be consistent with Uniswap V3, ensuring compatibility and a proven, efficient model. The revenue allocated to Liquidity Providers is claimable and sent to their trading wallet upon claiming.


# Setting Your Liquidity Range on radFi

{% embed url="<https://www.youtube.com/watch?v=d93Z79ZmBSk>" %}

One of its most powerful features is the ability to set a custom price range for your liquidity — giving you full control over how your Bitcoin and tokens are allocated.

### How It Works

When you provide liquidity on radFi, you’ll be asked to choose a price range. We have a few preset options or you can fully customize

* Full range (default) – Your assets are spread evenly across all prices.
* Preset ranges - Toggle between the 5%, 10%, and 20% width from the current price
* Custom range – Drag the sliders or enter prices into the textbox to fully customize the range you want.

Here’s the key idea:

* Min price → the point where you finish buying your selected token. At this level, all of your Bitcoin has been sold into whatever token you’ve selected.
* Max price → the point where you finish selling your selected token. By the time the price reaches this level, you’ve fully rotated back into Bitcoin and have sold all of your selected token.

Between your Min and Max, your position is a mix of both assets. In effect, you are dollar-cost averaging (DCA) into and out of your selected token over the price range you choose. Custom ranges can be a helpful tool to help you execute the trading strategy that meets your goals - whether that’s buying/selling aggressively at certain prices or more passively buying/selling at nearly any price.


# Trading Wallet

Before interacting with radFi, the first step is to create and fund a **Trading Wallet**. These wallets are **2/2 multisignature wallets**, where one signature comes from your connected wallet and the other from the radFi multisignature wallet. The signature requirement from radFi expires after 3 months.

<figure><img src="/files/DdAXiK6a3JfkyYyo2fpr" alt=""><figcaption><p>When you connect your BTC wallet for the first time, the UI will prompt you to create a trading wallet.</p></figcaption></figure>

Once radFi’s signature expires, users will be able to withdraw funds, even in the worst-case scenario where radFi is unable to sign trading wallet transactions. At this point, users will be prompted to refresh their trading wallet in order to continue using radFi.

The Trading Wallet setup enables radFi to offer a **near-instant trading experience**, thanks to greater confidence in the finality of broadcast transactions. After funding your trading wallet, you can start interacting with radFi.


# Trading

After funding their trading wallet, users can begin executing trades on radFi. When a user enters a trade, radFi displays a quote. If satisfied, **the user confirms the swap and signs a PSBT**.

<figure><img src="/files/Qu9687vy0QvgHtiuvLoe" alt=""><figcaption><p>You can make deposits from your BTC wallet to the trading wallet on the portfolio page</p></figcaption></figure>

<figure><img src="/files/Rt60z1kENxcIdt5NgfJY" alt=""><figcaption><p>If the user accepts the quote, they can sign the PSBT (confirm swap) to proceed with the transaction</p></figcaption></figure>

radFi then checks if the quote is still valid. If it is, radFi signs the PSBT. Once both parties have **signed the PSBT and it’s broadcast**, the user will **immediately see their updated spendable balance** in the trading wallet and can continue trading.

*Note: If the quote is no longer valid due to other user activity, the trade request will be dropped. The user will need to request a new quote and submit another PSBT.*


# Automated Market Making

After funding their trading wallet, users can start leveraging radFi’s AMM. Users select a pool to contribute to on the **Pools tab** (e.g., PUPS/BTC), along with their **desired range** and **asset amounts**. The required amounts are based on the current price quoted by the AMM. Once confirmed, users broadcast and sign a PSBT from their trading wallet.

<figure><img src="/files/7LLwlZq2ZIMrIP4i48av" alt=""><figcaption><p>Users can become LPs by adding liquidity to their preferred pair</p></figcaption></figure>

radFi then checks if the request is still valid according to the latest quote. If valid, radFi signs the PSBT. Once signed by both parties and broadcast, the user will immediately see their updated AMM position in the **Positions tab**.

As trading occurs within the user's **designated range**, they earn a share of the fees generated. Users can click "**Claim**" in the **Portfolio tab** to request their rewards. They’ll then be prompted to sign a PSBT for the rewards. radFi checks if the request is valid based on the rewards earned, and if so, signs the PSBT and broadcasts the transaction.

*Note: The asset amounts contributed at a given range are determined by radFi’s quoted price at the time of the request. If the quote becomes invalid due to other user activity, the AMM request is dropped. The user must request a new quote and submit another PSBT.*


# Virtual Mint

## Overview

radFi Virtual Mint is a feature designed to solve the problem of capital leaving the onchain bitcoin ecosystem during token mints. The current token minting experience exports millions of dollars in BTC from the community to miners as transaction fees.

The Virtual Mint feature solves this problem. It mirrors the exact user experience of etching and minting tokens on mainnet, however, the minting takes place off chain in a backend server that operates a virtual mempool. Users participate in mints just as they would on mainnet, but instead of spamming the network and burning sats on fees, we utilize the committed BTC for a liquidity pool on the radFi AMM and make it available for trading. This solves the problem of liquidity exportation while still maintaining key benefits of a free and open mint.

Our three goals are:

* Liquid Markets
  * Retain liquidity within the Bitcoin ecosystem while ensuring that liquidity is readily available to trade for new tokens
* Transparency and Equal Access
  * Enable sustainable token launches via fair and open minting, creating a better distribution of tokens across holders through our time-based auctions
* Reward the Community
  * Reward token creators and initial minters who don’t sell by sharing trading fees earned from the locked liquidity position

## Etching

When a user wants to etch a rune and start a mint, the following occurs:

1. Some parameters are selected by the user, and some are fixed default values controlled by radFi
   1. Fixed parameters
      1. Percent allocation to liquidity pool = 20%
      2. Premine percentage = 100%
      3. Decimals = 2
      4. Token supply = 1,000,000,000
      5. Number of tokens per mint transaction = 10,000
   2. Flexible parameters
      1. Display Ticker
         1. This section is used by the radFi frontend and is not included onchain. This could be considered a "nickname", or shorter version of the rune's ticker
            1. ![](https://lh7-rt.googleusercontent.com/docsz/AD_4nXc6MpDYxjBCp2ambdj1FJfifeTluGbNIgdrv6ZGxd9jyQCMbJPPrthiZuZnoHwreP2Ael0YAjqVxie_bGFQwZNEjPPwDVZmHP1nWVlztyiHrpm7RyXJ551fSNTOQ5I1PwlvzHtH?key=D9wZk4k5SLqFOBRNE6SGOA)
      2. Rune Ticker
      3. Token Image
      4. Symbol
         1. (e.g. 😂)
      5. Description
      6. Socials (optional)
         1. Twitter
         2. Telegram
         3. Website
2. The user submits the etch transaction and the 100% premine is directed to radFi-controlled wallets
3. The virtual minting process begins

## Minting & Virtual Mempool

radFi operates a virtual mempool to process all virtual mints. It is a single, global virtual mempool for all virtual mints occurring on the platform; each individual mint does **not** have its own mempool.&#x20;

radFi integrates a Bitcoin API requesting Bitcoin blocks and their timestamps. When a Bitcoin block is mined on mainnet, radFi also mines a virtual block.

When a user wants to participate in a mint, they transfer the amount of BTC they want to spend along with data that includes the rune they want to mint, how many mints, and the amount of BTC.

The server then adds the transaction data to the virtual mempool. The BTC spent is deducted from the user’s Spendable Balance on their radFi Trading Wallet, just as it would be for a swap. Once a Bitcoin block is mined on mainnet, we mine a virtual block mimicking the same rules as Bitcoin mainnet:

* The maximum number of mint requests per block is 4,000
* The 4,000 requests with the highest bids are included in the block
* Only messages broadcast prior to the timestamp of the Bitcoin block are eligible
* Any messages not included in the latest block stay in the virtual mempool

When the minting period has ended (either from time or from minting out), radFi takes the following steps:

1. All remaining mint requests for that rune are dropped from the virtual mempool
2. Check if the total fees committed by users exceeds the *minLiq* parameter
   1. If it does not exceed *minLiq*, BTC paid by users is refunded and made available to be claimed back by the users to their trading wallet and the process ends
3. Calculate refund amounts and make available for users to claim
   1. Refund amount = btcSpent - btcConfirmed
   2. Users pay the fee for the refund claim tx
4. Calculate amount of tokens owed to each user and initiate batch transfers to distribute
   1. If the time limit (5 days) and *minLiq* (0.08 BTC) are met, but the token does not mint-out (less than 800M tokens are minted), the remaining unminted tokens are distributed pro-rata based on amount minted to those that participated in the mint
5. Pair the committed BTC with the remaining tokens (200M) and supply as liquidity in a new radFi pool

## Diamond Hands

Diamond Hands is a unique feature that adds game theory mechanics to the radFi launchpad. At the end of a mint, tokens are distributed to users. All unsold, untransferred tokens are automatically put into Diamond Hands status and will start receiving their share of rewards from the locked LP, **so long as the user minted at least 1M tokens.**

Once sold or transferred, those tokens can never regain Diamond Hands status. This creates an interesting scenario where the users that hold the longest can end up getting a growing portion of ongoing trading fees. Your share of the Diamond Hands allocation continues to grow as other users sell or transfer their tokens.

**Walkthrough Example**

A new token is launched through radFi’s virtual mint with a total supply of 1,000,000,000 tokens. After the mint ends, 20% is allocated to the AMM liquidity pool. The remaining 800,000,000 tokens are distributed to participants.

Participant allocations:

* Alice minted 1M tokens
* Bob minted 2M tokens
* Carol minted 3M tokens
  * All other tokens are held by users that minted less than 1M tokens (the minimum requirement for Diamond Hands status)

**Status breakdown**: 6M tokens are in Diamond Hands status receiving 33.33% of trading fees

* Alice has 1M tokens with Diamond Hands status
* Bob has 2M tokens with Diamond Hands status
* Carol has 3M tokens with Diamond Hands status

After launching:

* Alice sells 200k tokens, then buys back 300k tokens
  * \*Note\* - buying tokens off the secondary market does not increase your Diamond Hands allocation
* Bob sells 500k tokens
* Carol holds all her tokens without selling or transferring

**Status breakdown:** 4.5M tokens are in Diamond Hands status receiving 33.33% of trading fees

* Alice: still holds 1.1M tokens, but **none are eligible for diamond hands** because she is below the 1M tokens held since mint requirement
* Bob: 1.5M tokens retain Diamond Hands status
* Carol: 3M tokens retain Diamond Hands status

## Parameters

**Token Supply**&#x20;

* 1,000,000,000 for all tokens etched on our platform

**Mints per block**&#x20;

* 4,000 mints maximum will be accepted into a virtual block, and a virtual block is mined every time a Bitcoin block is mined

**Virtual Block Inclusion Rules**&#x20;

* The 4,000 mints with the highest bids will be included each block. In the case of a tie, timestamp is used.

**Minimum price per token**

* The minimum price per token you can submit during the minting process is 0.01 sats

**Tokens per mint**

* Each submitted mint is for 10,000 tokens

**Mints per transaction**

* A transaction can be submitted for 1 mint minimum and 1,000 mints maximum

**Minimum Liquidity for a Launched Token**

* For a token to launch, it must reach a minimum of 0.08 BTC committed

**Launched pool parameters**

* When a token is launched, the BTC committed is paired with 20% of the token supply and supplied at a full range liquidity position. The fee tier of the pool is set to 1.00%.

**Diamond Hands Required Holdings**&#x20;

* In order to be eligible for Diamond Hands Rewards, you must mint, hold, and not transfer at least 1,000,000 tokens

**Revenue from Locked LP**

* Token Deployer = 50.0%
* Diamond Hands Holders = 50.0%


# Virtual Mint FAQ

**What is King of the Block?**

* King of the Block (KOTB) is determined by whichever token has the most mints included in the current virtual block
  * If multiple tokens have the same number of mints included, KOTB is determined by whichever token has the most BTC committed to those mints
  * If multiple tokens have the same number of mints included **and** the same amount of BTC committed to the mints, KOTB is determined by whichever token has the earliest submitted mint transaction included
* The "Runner up" token displayed is all the above but for the second most popular token

**Why can't I mint a newly created token right away?**

* Based on the [runes token specification](https://docs.ordinals.com/runes/specification.html), once an etching transaction is submitted it requires at least **six** bitcoin block confirmations to be "revealed" (this is to prevent front running)
* Once revealed, our platform requires **one** additional bitcoin block confirmation post-reveal before becoming mintable

**What does the "Cheap etch" switch do when creating a new token?**

* This feature acts as an additional check for you to ensure you don't accidently etch an overly expensive image. The cheap etch option is “on” by default, and restricts the size of the image you submit so that you pay no more than $30 for an etching transaction.

  When it’s “off”, you can submit an image of any size.


# User Flows


# Swap Tab

### 1. C**onnect Wallet**

<figure><img src="/files/3m7szl2X3WuU7SDoOl33" alt=""><figcaption><p>The user chooses an external BTC wallet to connect to the site</p></figcaption></figure>

<figure><img src="/files/5tW3SeQBSp62AZFhhS2Y" alt=""><figcaption><p>The user must agree to radFi's terms of use</p></figcaption></figure>

### **2. Create & Fund Trading Wallet**

<figure><img src="/files/dGMMeZGto186nj2KPaYn" alt=""><figcaption><p>Once the trading wallet is created, click fund your trading wallet</p></figcaption></figure>

<figure><img src="/files/jdoqfTmSbWlKyK1TBEPm" alt=""><figcaption><p>Clicking deposit will bring up a prompt to transfer BTC into the trading wallet</p></figcaption></figure>

<figure><img src="/files/FVyIFPSKm8ka1LBvrLzx" alt=""><figcaption><p>Once submitted, the transaction will be broadcast to the Bitcoin network, along with a link to track the confirmation progress. The balance will be reflected on the portfolio page</p></figcaption></figure>

### **3. Perform Swap**

<figure><img src="/files/aDhVc2lAUty8VBngf7B5" alt=""><figcaption><p>With a funding wallet created, the user can swap for a token under the "Swap" tab</p></figcaption></figure>

<figure><img src="/files/FxiLabh4XGnE7ukps5i6" alt=""><figcaption><p>The user has a chance to review swap details before confirming the swap</p></figcaption></figure>

<figure><img src="/files/AxjJZa9NbyixhpoeByqV" alt=""><figcaption><p>Once submitted, the transaction will be broadcast to the Bitcoin network, along with a link to track the confirmation progress. The balance will be reflected on the portfolio page</p></figcaption></figure>


# Pool Tab


# Positions

### **Add Liquidity**

<figure><img src="/files/ZeFJu39OlMJI0hT4rZ8J" alt=""><figcaption><p>The user can supply a liquidity pair, along with their desired liquidity range</p></figcaption></figure>

### **Liquidity List**

<figure><img src="/files/0B8OqW2r3iaR3mqtlP2Z" alt=""><figcaption><p>A list of your LP pairs</p></figcaption></figure>

### Liquidity Pair Details

<figure><img src="/files/yj7l1qMLCYg5qYIjYdtl" alt=""><figcaption><p>Details of each pair</p></figcaption></figure>


# Pools

### **LP Pools List**

<figure><img src="/files/THocDAEvl34yxyGHphXG" alt=""><figcaption><p>A list of existing LP pools on radFi</p></figcaption></figure>

### **LP Pair Details**

<figure><img src="/files/RlUiuH1oO23j5BsW6eQr" alt=""><figcaption><p>Pool details</p></figcaption></figure>


# Portfolio Tab

### **Deposit & Withdraw**

<figure><img src="/files/oxU0eYmzfgXUGoNkcsay" alt=""><figcaption><p>The user can deposit into their trading wallet or withdraw back to their external wallet. This applies to both $BTC and any rune tokens the user holds </p></figcaption></figure>

<figure><img src="/files/w3Vq6s8gI3VEEccGHnGi" alt=""><figcaption><p>Once submitted, the transaction will be broadcast to the Bitcoin network, along with a link to track the confirmation progress.</p></figcaption></figure>


# Mint Tab


# Virtual Mint - Overview

### **Tooltips**

<figure><img src="/files/9hhoHCw7obkL1QVY3wxy" alt=""><figcaption></figcaption></figure>

1. **Create a new token**
   1. when a user clicks this button, it brings them to the “Etch” page
2. **Top row**
   1. this row moves to the left, showing the most recent mint activity on the platform (20 most recent mint transactions)
      1. when a new tx is submitted, it replaces the oldest tx in the row
      2. clicking on any of these "recent activity" cards brings you to that token's "Mint" page
3. **King of the block**
   1. this section shows which token has the most mints in the current block
      1. if the token has been the most popular for multiple consecutive blocks we’ll add a fire emoji next to it and say how many blocks in a row it’s been “king of the block”
      2. below it “Runner up...” is the second most popular mint in the current block
         1. we also display how many more mints are needed from the Runner up to become King of the Block
4. **Current block details**
   1. the progress bar should what % of the current VM block is full, how many mints are in the current block and the estimated time left in the current block (based on mainnet estimates)
   2. bid price/token section shows 3 things:
      1. minimum = minimum sats price per token to be included in the current block (projected)
      2. average = the average bid price per token in sats of all the mints in the current block
      3. highest = the highest bid price someone is paying per token in sats in the current block
5. **My transactions**
   1. you should be able to sort by minting (default), launched and failed
      1. minting = this section shows two types of transactions
         1. unconfirmed: the user’s transactions that are currently pending in our mempool or are in the current block (but not confirmed)
         2. confirmed: the user’s transactions that are confirmed but it’s token is still minting
      2. launched = these are successfully confirmed transactions that were for tokens that minted out and made it to radFi AMM
         1. when a user clicks “view pool on radFi”: it brings the user to the radFi swap page with the minted token being the sell asset and the buy asset being BTC
      3. failed = these are successfully confirmed transactions that were for tokens that DIDNT mint out and have BTC to claim
         1. when a user clicks “claim your BTC”: a tx is triggered prompting the user to claim their committed BTC
6. **Bottom row of blocks**
   1. these blocks are displayed chronologically, with the current block in the middle (and open), then the previous VM blocks on it’s left and the next VM block on the right
      1. blocks the have been confirmed show the image of the most popular token from that block, as well as how many other unique tokens had mints in that block
      2. blocks that haven’t been confirmed yet show the ?
   2. when you hover over a confirmed block it will show the # of mints in that block, the amount of BTC committed in that block and the display name of the most popular mint
   3. when the current block is complete, we’ll have an animation that closes the current block, shifts all blocks 1 to the left and then an animation that opens the new block
7. **Sorting drop-down**
   1. you should be able to sort by trending (default), progress, new, marketcap, failed and ending soon
      1. trending = tokens with the most mint activity in the past 7 days, the most popular at the top
      2. progress = tokens closest to minting out, the closest token at the very top
      3. new = tokens most recently created, the newest token being at the very top
      4. marketcap = tokens that already minted out (and now trading on radFi), sorted by highest marketcap at the top
      5. failed = all the tokens that ended and weren’t successfully launched
      6. ending soon = tokens that have the least amount of time remaining before their mint closes, the least amount of time left being the very top
8. **Search bar**
   1. this search bar lets you search any token created on radFi VM
9. **Right column token cards**
   1. these are the tokens minting through our platform, sorted based on which sorting category is picked (#7)
      1. clicking on these tokens will bring you to the token's "Mint" page
   2. we provide a progress bar to show how close the token is to minting out
      1. complete = % already confirmed in a VM block
      2. pending = % currently pending, whether it's in the current block or waiting in our mempool
      3. remaining = 100% - complete
   3. in the top right we show how much time is left before the mint is over (if it doesn’t sell out)
      1. a highlight goes over the time left part of any token card with less than 24 hours remaining for it’s mint
         1. if greater than or equal to 24 hours remain, it displays “\[X] days left”
         2. if greater than or equal to 1 hour, but less than 24 hours remain, it displays “\[X] hours left”
         3. if less than 1 hour remains, it displays “\[X] minutes left”
   4. on the token’s picture we display the token’s implied marketcap
      1. BTC confirmed \* btc\_price \* 5 = implied marketcap
   5. when you hover over a token it has a slight purple highlight
10. **Block visualizer**
    1. (this is just a placeholder that we’ll go live with)
11. **Claim all BTC**
    1. claims all BTC from failed or only partial filled mints (that are completed)
       1. only lights up when there’s something to claim


# Virtual Mint - Minting

### Tooltips

<figure><img src="/files/uC4ZNQe9tT1tkfmSLPNn" alt=""><figcaption></figcaption></figure>

1. **Back button**&#x20;
   1. this button brings you back to the “Overview” page&#x20;
2. **Token information**&#x20;
   1. this section describes all the main relevant information about the token&#x20;
      1. logo&#x20;
      2. symbol&#x20;
      3. Display ticker - we collect this info when a user etches (but it’s not a part of the Rune info)
      4. Rune ticker&#x20;
         1. user should be able to click on this to copy&#x20;
      5. description&#x20;
      6. created: the time when the Rune is confirmed as etched&#x20;
      7. creator address: the address that submitted the etch&#x20;
         1. user should be able to click on this to copy&#x20;
      8. Rune ID: ID of the Rune&#x20;
         1. user should be able to click on this to copy&#x20;
      9. socials: links to Twitter, Telegram or website (if provided)&#x20;
         1. \*if not provided, make 50% opaque but still visible&#x20;
3. **Current block**&#x20;
   1. similar to the overview page, this progress bar shows how many mints are in the current VM block, approximately what % of the block that is, and the estimated time remaining in the current block&#x20;
4. **Current block prices**&#x20;
   1. similar to the overview page, it shows 3 things:&#x20;
      1. minimum = minimum sats price per token to be included in the current block (projected)
      2. average = the average bid price per token in sats of all the mints in the current block&#x20;
      3. highest = the highest bid price per token someone is paying in sats in the current block&#x20;
5. **Confirmed \[token] mint prices**&#x20;
   1. this section displays historical bid prices per token for this specific token&#x20;
      1. lowest = lowest bid price per token for a confirmed mint of this specific token&#x20;
      2. average = average of all confirmed bid prices per token of this specific token&#x20;
      3. highest = highest bid price per token for a confirmed mint of this specific token&#x20;
6. **Bid price graph**&#x20;
   1. (currently a placeholder)&#x20;
7. **Mint progress**&#x20;
   1. this displays the progress of the token&#x20;
      1. shows the implied marketcap of the token, based on the amount of BTC currently confirmed (with the assumption it mints out): BTC confirmed \* btc\_price \* 5 = implied marketcap&#x20;
      2. ENDS IN is a countdown timer for how much time is left in the token’s mint&#x20;
      3. we provide a progress bar to show how close the token is to minting out&#x20;
         1. complete = % already confirmed in a VM block&#x20;
         2. pending = % currently pending, whether it's in the current block on waiting in our mempool&#x20;
         3. remaining = 100% - complete&#x20;
8. **Mint**&#x20;
   1. this is where the user goes to mint the token&#x20;
      1. “Number of mints” is where the user inputs how many mints they want&#x20;
         1. this should default to 0&#x20;
         2. hitting the buttons to the right increase or decrease the mint amount by 1&#x20;
         3. hitting the buttons below are a shortcut to inputting more mints&#x20;
            1. the default highlighted button is “reset”&#x20;
            2. reset brings their input back to 0&#x20;
            3. max is the max amount they can mint based on their current set price per token&#x20;
         4. the user shouldn’t be allowed to input any decimals&#x20;
         5. underneath where it says mint(s) we display how many tokens they would be minting given their input&#x20;
      2. “price per token” is where the user inputs how much they want to pay per token&#x20;
         1. this should default to 0.01 sats/token&#x20;
         2. hitting the buttons to the right increase or decrease the price by 0.001 sat&#x20;
         3. hitting the buttons below are a shortcut to changing your price per token&#x20;
            1. the default highlighted button is “slow”&#x20;
            2. slow is the minimum bid price per token required to be included in the current block
            3. medium is SLOW \* 1.5 (rounded up to 3 decimal places)&#x20;
            4. fast is SLOW \* 2&#x20;
            5. when a user clicks on custom it will set the input to blank and they’ll be required to input something&#x20;
         4. the user should only be allowed to input 3 decimals maximum&#x20;
         5. implied marketcap under “sat(s)” should be a dynamic calculation: (price per token \* 800,000,000)/100,000,000 \* BTC price \* 5&#x20;
      3. the information box below the inputs informs the user if their mint is projected to be included in the current block or not&#x20;
         1. if price per token is **greater than or equal to** the minimum bid price per token required to be included in the current block, display (in green):&#x20;
            1. Your mint is projected to be included with that price!&#x20;
            2. Mints are not guaranteed... but if your transaction is never confirmed in a block or the token doesn’t mint out, you will be able to claim a full refund.&#x20;
         2. if price per token is **less than** the minimum bid price per token required to be included in the current block, display (in red):&#x20;
            1. Your mint is **not** projected to be included with that price!&#x20;
            2. Mints are not guaranteed... but if your transaction is never confirmed in a block or the token doesn’t mint out, you will be able to claim a full refund.&#x20;
      4. amount you’ll pay is a function of:&#x20;
         1. number of mints \* number of tokens per mint \* price per token / 100000000&#x20;
         2. the user’s balance is displayed below this output in parentheses&#x20;
      5. mint button&#x20;
         1. the number on the mint button changes to match the amount of tokens the user is trying to mint&#x20;
         2. there are a couple scenarios this mint button will show:&#x20;
            1. **invalid price per token**: shown when a price with more than 3 decimal places is submitted&#x20;
            2. **insufficient BTC balance**: when the user’s # of mints \* price per token \* 10,000 is greater than the user’s radFi BTC balance (after transaction fees)&#x20;
            3. **you must spend a minimum of 546 sats**: if a user’s tx size is too small (under 546 sats)&#x20;
9. **Transactions**&#x20;
   1. this sections shows all the VM mint transactions related to the specific token&#x20;
      1. all mints&#x20;
         1. shows mint transactions from all accounts for the specific token, displayed where the most recent tx is at the top&#x20;
      2. my mints&#x20;
         1. shows mint transactions from only the user’s connected account for the specific token, displayed where the most recent tx is at the top&#x20;
      3. claim BTC&#x20;
         1. this button will appear when a mint is complete, whether successful or failed, if the user has any BTC to claim&#x20;
10. **Top holders**&#x20;
    1. this shows the confirmed balance for the top 50 holders of the token, with the highest balance being the top of the list&#x20;
    2. the top right “total” shows the # of wallets with a balance > 0 of the token&#x20;
11. **Diamond hands**&#x20;
    1. this section explains what the diamond hand feature is, as well as shows the top diamond hand wallets&#x20;
       1. “learn more” in the description should link to the radFi docs section explaining what the feature is&#x20;
       2. the list of diamond hand wallets shows what % of diamond handed tokens they own, sorted from largest to smallest&#x20;
          1. we also display the # of diamond handed wallets, those who still have 1M+ diamond handed tokens&#x20;
          2. the % calculation is done as follows:&#x20;
             1. users # of diamond handed tokens / total # of diamond handed tokens \* 100


# Virtual Mint - Etching

### Tooltips

<figure><img src="/files/OtIQv2UGew5QlmmeAZal" alt=""><figcaption></figcaption></figure>

1. **Back button**
   1. this button brings you back to the “Overview” page
2. **Token Image**
   1. should follow the standard rules for etching Rune “images”
   2. our “cheap etch” switch should default to the “on” position
      1. when it’s “on”, we should restrict the size of the user’s submitted image to whatever size would result in a $30 maximum etch transaction
      2. when it’s “off”, the user can submit an image of any size
3. **Symbol**
   1. should follow the standard rules for etching Rune “symbols”
4. **Display ticker**
   1. this is a name we allow the user to choose for their token that we will display on our frontend
5. **Rune ticker**
   1. should follow the minimum/maximum limit of characters for etching Rune “tickers”
6. **Description**
   1. input box for users to add a description of their token
7. **Socials (optional)**
   1. twitter
   2. telegram
   3. website
8. **FAQ 1 - What is Virtual Mint?**
   1. Virtual Mint mirrors the exact user experience of etching and minting Runes on Bitcoin mainnet, however, the minting takes place off chain in a backend server that operates a “virtual mempool”. Users participate in mints just as they would on mainnet, but instead of their transaction fees going to miners, we aggregate their theoretical transaction fees into a liquidity pool on the radFi AMM and make it available for trading.
9. **FAQ 2 - Why use Virtual Mint?**
   1. Virtual Minting is a more efficient platform for launching Runes tokens on Bitcoin. Users pay less to mint each Rune (since you can bundle multiple mints into one transaction), while also seeding a liquidity pool for secondary market trading during the process. This ensures deep liquid markets from day one, boosting onchain activity, and putting an end to the liquidity drain on the Bitcoin ecosystem. Public, time-based bidding makes it hard for insiders to dominate the initial supply, ensuring fairer distribution.
10. **FAQ 3 - What happens when I create a token?**
    1. When you create a token through Virtual Mint, the token is etched on Bitcoin mainnet and 100% of the supply will be premined to radFi. After that the minting process will begin.
    2. Users select how many mints and what price per token they want to bid, then submit that transaction to Bitcoin mainnet. If their price per token is high enough to be included in a Virtual Block, their transaction will be confirmed and they will be subject to receiving their minted tokens if the token launches.
    3. If your token mints out 80% of it’s supply and reaches the minimum required marketcap, the confirmed Runes mints will be distributed to minters and the committed BTC will be paired with 20% of the supply on the radFi AMM. If the etched token does not reach the quorum, the etched Runes will not be distributed and the committed BTC will be available to claim.
11. **hFAQ 4 - Terms and Conditions**
    1. (Links to open a new tab for our terms and conditions in our docs)
12. **Cost to etch warning**
    1. the cost should be dynamic, based on the size of the user’s image and the market fee rate
13. **Submit button**
    1. clicking this will prompt an Xverse transaction to pop up requiring the user to sign to submit their etching transaction


# Walk-Through Video

{% embed url="<https://dy9ru483ncly8.cloudfront.net/radFi_walk_through.mp4>" %}


# Automated Market Maker

radFi operates an Automated Market Maker, meaning that radFi is **always online** to quote prices for users interested in trading Runes.

Users that want to leverage radFi’s AMM will submit a PSBT from their trading wallet containing the assets they want to trade (e.g. 100,000 PUPS & 0.10 BTC) along with `OP_12` data containing their indicated upper and lower bound trading ranges (e.g. 50 sats <> 500 sats).

When traders request a quote, the price offered by radFi for a given asset pair (e.g. BTC/bUSD) is determined by a variation on the constant product formula (x\*y=k) known as [**Concentrated Liquidity**](https://docs.uniswap.org/concepts/protocol/concentrated-liquidity). Concentrated Liquidity allows LPs to set a custom range through which their assets will be made available for trading. radFi then executes their selected strategy when other traders request quotes.

The Concentrated Liquidity calculations themselves are implemented in an **EVM environment**. When a user requests a quote, the radFi AMM queries the EVM environment to determine the correct quote for the given trade request.


# radFi OP\_12

radFi utilizes `OP_12` as a key component of its Automated Market Maker (AMM) system. Unlike its traditional usage as a simple numeric constant in Bitcoin Script, radFi repurposes `OP_12` to encode structured AMM data within dust value outputs. This allows RadFi to facilitate decentralized token AMM using Bitcoin-native transactions.

### How radFi Uses OP\_12

In radFi’s AMM design, a dust value output containing `OP_12` encodes details such as:

* **Transaction type** (provide liquidity, swap, withdraw liquidity)
* **Input and output asset amounts**
* **Token identifiers**
* **Trading fee information**
* **Price range for limit orders** (if applicable)

This approach enables fully on-chain AMM execution while remaining lightweight and efficient.

### OP\_12 Structure

Each radFi transaction contains at least 1 `OP_12` data output formatted as follows:

```bash
# RadFi AMM Script 
# Example: Swap Rune for BTC
OP_12 # RadFi Flag (Indicates a RadFi transaction)
OP_PUSHBYTES_23 # Mark the size of swap data
02818080b08a83b7f6d39801930a4564ac8636bf020000 # Swap data

```

### Innovating Bitcoin DeFi

radFi’s repurposing of `OP_12` for encoding demonstrates how Bitcoin Script can be extended to support DeFi applications on-chain natively, without smart contracts. By embedding structured data in dust UTXOs, radFi enables efficient AMM-based txs while maintaining the security and transparency of Bitcoin’s UTXO model.

For the complete radFi `OP_12`usage structure, refer to [**Bitcoin Data Availability**](/technical-architecture/bitcoin-data-availability)**.**


# Bitcoin Data Availability

Each trading transaction includes the tokens from the **trading wallet** and **the pool**, **a sequence number**, and **Uniswap data**. Users can compare the balance change between transaction inputs and outputs with Uniswap data, and compare the Uniswap data with the EVM contract to verify the Bitcoin transaction. Below is the structure of each type of trading transaction:

## 1. Init Pool (Create a New Pool and Add Liquidity)

**Inputs:**

* **Input 0:** Pool init UTXO
* **Remaining Inputs:** UTXOs from the trading wallet to init the pool

**Outputs:**

* **Output 0:** New pool init UTXO
* **Outputs 1-10:** New pool liquidity UTXOs
* **Outputs 11 to m:** radFi`OP_12` outputs containing pool initialization data
* **Output m+1:** `Runestone OP_RETURN`
* **Output m+2:** Trading wallet’s rune change
* **Output m+3:** Trading wallet’s Bitcoin change

**Example:** [**Init Pool and Add Liquidity**](https://mempool.space/tx/f3c9652a0cdab3ed7e759db12f092899248806b08e4bd50c8a2b7b3f10eb7a75)

**Inputs:**

* **Input 0:** Pool init UTXO (from the previous pool init transaction)
* **Input 1:** Trading wallet’s UTXOs containing Bitcoin funds to provide liquidity for the new pool and cover the transaction fee
* **Input 2:** Trading wallet’s UTXO containing Rune tokens to provide liquidity for the new pool

**Outputs:**

* **Output 0:** New pool init UTXO (for use in the next pool init transaction)
* **Outputs 1-5:** New pool liquidity UTXOs containing the liquidity added for the new pool
* **Outputs 6-10:** Additional pool liquidity UTXOs, but containing no liquidity due to the limit on the `Runestone OP_RETURN` script size
* **Outputs 11-12:** Pool initialization data, combining two `ScriptPubKey` data that follow `OP_12`:
  * **Byte 0 (Flag):** 1 (indicates providing liquidity)
  * **Bytes 1-4 (UpperTick, big endian int32):** 887200
  * **Bytes 5-8 (LowerTick, big endian int32):** -887200
  * **Remaining Bytes:** Contain 11 `uvarint128` values:
    * **Amount0:** 10000964745485757151
    * **Amount1:** 1000
    * **InitPrice:** 792243410400884224932
    * **Token0Decimal:** 18
    * **Token1Decimal:** 8
    * **SequenceNumber:** 57
    * **Fee:** 100 (1%)
    * **Token0Id - Block:** 885548
    * **Token0Id - Tx:** 319
    * **Token1Id - Block:** 0
    * **Token1Id - Tx:** 0
* **Output 13:** `Runestone OP_RETURN` containing data for the liquidity token in the new UTXOs and Rune change for the trading wallet
* **Output 14:** Trading wallet’s Rune change UTXO, containing any remaining Rune tokens
* **Output 15:** Trading wallet’s Bitcoin change UTXO, containing any remaining Bitcoin

## 2. Add New Liquidity Position to an Existing Pool

**Inputs:**

* **n inputs (n <= 10):** Pool liquidity UTXOs
* **Remaining inputs:** Trading wallet’s UTXOs to add liquidity

**Outputs:**

* **n outputs:** New pool liquidity UTXOs containing the added liquidity
* **Outputs n to m:** radFi `OP_12` outputs containing added liquidity data
* **Output m+1:** `Runestone OP_RETURN`
* **Output m+2:** Trading wallet’s rune change
* **Output m+3:** Trading wallet’s Bitcoin change

**Example:** [**Add New Liquidity Position**](https://mempool.space/tx/27f9522159a4c13d01a42dca5658535dfb6c5adc26f92a4191d09292bbee5af7)

**Inputs:**

* **Inputs 0-3:** Pool liquidity UTXOs
* **Input 4:** Trading wallet’s UTXOs containing Bitcoin funds to provide liquidity for the pool and cover the transaction fee
* **Input 5:** Trading wallet’s UTXO containing Rune tokens to provide liquidity for the pool

**Outputs:**

* **Outputs 0-3:** New pool liquidity UTXOs containing the liquidity added for the pool
* **Output 4:** radFi `OP_12` script containing add liquidity data:
  * **Byte 0 (Flag):** 1 (Providing liquidity flag)
  * **Bytes 1-4 (UpperTick, big endian int32):** 887200
  * **Bytes 5-8 (LowerTick, big endian int32):** -887200
  * **Remaining bytes:** Contain 11 `uvarint128` values:
    * **Amount0:** 10000964745485757151
    * **Amount1:** 1000
    * **InitPrice:** 0
    * **Token0Decimal:** 18
    * **Token1Decimal:** 8
    * **SequenceNumber:** 58
    * **Fee:** 100 (1%)
    * **Token0Id - Block:** 885548
    * **Token0Id - Tx:** 319
    * **Token1Id - Block:** 0
    * **Token1Id - Tx:** 0
* **Output 5:** `Runestone OP_RETURN` containing data for the liquidity token in the new UTXOs and Rune change for the trading wallet
* **Output 6:** Trading wallet’s Rune change UTXO containing any remaining Rune tokens
* **Output 7:** Trading wallet’s Bitcoin change UTXO containing any remaining Bitcoin

## 3. Swap in One Pool:

**Inputs:**

* **n inputs (n <= 10):** Pool liquidity UTXOs
* **Remaining inputs:** Trading wallet’s UTXOs to swap

**Outputs:**

* **n outputs:** New pool liquidity UTXOs
* **Outputs n to m:** radFi `OP_12` outputs containing swap data
* **Output m+1:** `Runestone OP_RETURN`
* **Output m+2:** Trading wallet’s token swap output
* **Output m+3:** Trading wallet’s rune change
* **Output m+4:** Trading wallet’s Bitcoin change

**Example:** [**Swap Bitcoin to Rune**](https://mempool.space/tx/ebdb718ee21e96462885b0e86ea5142e490350119a2dde8ed2a8ecddfe60180d)[**s**](https://mempool.space/tx/ebdb718ee21e96462885b0e86ea5142e490350119a2dde8ed2a8ecddfe60180d)

**Inputs:**

* **Inputs 0-1:** Pool liquidity UTXOs
* **Input 2:** Trading wallet’s UTXOs containing Bitcoin funds to swap and cover the transaction fee

**Outputs:**

* **Outputs 0-1:** New pool liquidity UTXOs
* **Output 2:** radFi `OP_12` script containing swap data:
  * **Byte 0 (Flag):** 2 (Swap flag)
  * **Byte 1:**
    * **First bit (IsExactIn):** true
    * **Remaining 7 bits (PoolsCount, uint8):** 1
  * **Remaining bytes:** Contain 8 `uvarint128` values:
    * **AmountIn:** 1000
    * **AmountOut:** 7444327141368519190
    * **SequenceNumber:** 66
    * **Fee:** 100 (1%)
    * **Input TokenId - Block:** 0
    * **Input TokenId - Tx:** 0
    * **Output TokenId - Block:** 885548
    * **Output TokenId - Tx:** 319
* **Output 3:** `Runestone OP_RETURN` containing data for the liquidity in the new UTXOs and Rune output for the trading wallet
* **Output 4:** Trading wallet’s swap output UTXO
* **Output 5:** Trading wallet’s Rune change UTXO (contains no Rune in this example due to no trading wallet’s Rune input)
* **Output 6:** Trading wallet’s Bitcoin change UTXO containing the remaining Bitcoin for the user

**Example:** [**Swap Runes to Bitcoin**](https://mempool.space/tx/3cefd10c34ff9f619177312a181bdffdd58cd699f81d23620815d0a55dd2aafd)

**Inputs:**

* **Inputs 0-3:** Pool liquidity UTXOs
* **Input 4:** Trading wallet’s UTXOs containing Bitcoin funds to cover the transaction fee
* **Inputs 5-6:** Trading wallet’s UTXOs containing Rune funds to swap

**Outputs:**

* **Outputs 0-3:** New pool liquidity UTXOs
* **Output 4:** radFi `OP_12` script containing swap data:
  * **Byte 0 (Flag):** 2 (Swap flag)
  * **Byte 1:**
    * **First bit (IsExactIn):** true
    * **Remaining 7 bits (PoolsCount, uint8):** 1
  * **Remaining bytes:** Contain 8 `uvarint128` values:
    * **AmountIn:** 11000000000000000000
    * **AmountOut:** 1299
    * **SequenceNumber:** 69
    * **Fee:** 100 (1%)
    * **Input TokenId - Block:** 885548
    * **Input TokenId - Tx:** 319
    * **Output TokenId - Block:** 0
    * **Output TokenId - Tx:** 0
* **Output 5:** `Runestone OP_RETURN` containing data for the liquidity in the new UTXOs and Rune change for the trading wallet
* **Output 6:** Trading wallet’s swap output UTXO
* **Output 7:** Trading wallet’s Rune change UTXO
* **Output 8:** Trading wallet’s Bitcoin change UTXO containing the remaining Bitcoin for the user

## 4. Withdraw Liquidity From a Pool

**Inputs:**

* **n inputs (n <= 10):** Pool liquidity UTXOs
* **Remaining inputs:** Trading wallet’s Bitcoin UTXOs to pay the transaction fee

**Outputs:**

* **n outputs:** New pool liquidity UTXOs
* **Outputs n to m:** `radFi OP_12` outputs containing withdraw liquidity data
* **Output m+1:** `Runestone OP_RETURN`
* **Output m+2:** Trading wallet’s withdrawn Rune liquidity
* **Output m+3:** Trading wallet’s withdrawn Bitcoin liquidity and Bitcoin change

**Example:** [**Withdraw Liquidity From a Pool**](https://mempool.space/tx/fe618d63b0d6e67d515fafe3ba6079ff3218cdd2dc0835fcdde596a09d4e6eb3)

**Inputs:**

* **Inputs 0-3:** Pool liquidity UTXOs
* **Input 4:** Trading wallet’s UTXOs containing Bitcoin funds to cover the transaction fee

**Outputs:**

* **Outputs 0-3:** New pool liquidity UTXOs
* **Output 4:** radFi `OP_12` script containing withdraw liquidity data:
  * **Byte 0 (Flag):** 3 (Withdraw liquidity flag)
  * **Remaining bytes:** Contain 10 `uvarint128` values:
    * **LiquidityValue:** 100004823610
    * **NftId:** 8
    * **Amount0:** 9621866398363189269
    * **Amount1:** 1039
    * **SequenceNumber:** 94
    * **Fee:** 100 (1%)
    * **Token0Id - Block:** 885548
    * **Token0Id - Tx:** 319
    * **Token1Id - Block:** 0
    * **Token1Id - Tx:** 0
* **Output 5:** `Runestone OP_RETURN` containing data for the liquidity in the new UTXOs and withdrawn Rune for the trading wallet
* **Output 6:** Trading wallet’s Rune change UTXO
* **Output 7:** Trading wallet’s withdrawn Bitcoin liquidity and Bitcoin change UTXO

## 5. Collect Fees From a Pool

**Inputs:**

* **n inputs (n <= 10):** Pool liquidity UTXOs
* **Remaining inputs:** Trading wallet’s Bitcoin UTXOs to pay the transaction fee

**Outputs:**

* **n outputs:** New pool liquidity UTXOs
* **Outputs n to m:** radFi `OP_12` outputs containing collect fees data
* **Output m+1:** `Runestone OP_RETURN`
* **Output m+2:** Trading wallet’s collected Rune fees
* **Output m+3:** Trading wallet’s collected Bitcoin fees and Bitcoin change

**Example:** [**Collect Fees From a Pool**](https://mempool.space/tx/21f4a5c60696c7be6d02da66991c77bc936febceac0989f1d1ccb28aa5accb35)

**Inputs:**

* **Input 0:** Pool liquidity UTXO
* **Input 1:** Trading wallet’s UTXOs containing Bitcoin funds to cover the transaction fee

**Outputs:**

* **Output 0:** New pool liquidity UTXO
* **Output 1:** radFi `OP_12` script containing collect fees data:
  * **Byte 0 (Flag):** 4 (Collect fees flag)
  * **Remaining bytes:** Contain 9 `uvarint128` values:
    * **NftId:** 8
    * **Amount0:** 52894105064729815
    * **Amount1:** 6
    * **SequenceNumber:** 93
    * **Fee:** 100 (1%)
    * **Token0Id - Block:** 885548
    * **Token0Id - Tx:** 319
    * **Token1Id - Block:** 0
    * **Token1Id - Tx:** 0
* **Output 2:** `Runestone OP_RETURN` containing data of liquidity in the new UTXOs and collected Rune fees for the trading wallet
* **Output 3:** Trading wallet’s collected Rune fees UTXO
* **Output 4:** Trading wallet’s collected Bitcoin fees and Bitcoin change UTXO

## 6. Increase Liquidity of a Position in a Pool&#x20;

**Inputs:**

* **n inputs (n <= 10):** Pool liquidity UTXOs
* **Remaining inputs:** Trading wallet’s UTXOs to increase liquidity

**Outputs:**

* **n outputs:** New pool liquidity UTXOs
* **Outputs n to m:** radFi `OP_12` outputs containing increase liquidity data
* **Output m+1:** `Runestone OP_RETURN`
* **Output m+2:** Trading wallet’s Rune change
* **Output m+3:** Trading wallet’s Bitcoin change

**Example:** [**Increase Liquidity of a Position in a Pool** ](https://mempool.space/tx/0a095158af50538d37abbd62bcc5e4ff7361a71d797a81a163cc36df1c9d8363)

**Inputs:**

* **Inputs 0-3:** Pool liquidity UTXOs
* **Inputs 4-5:** Trading wallet’s UTXOs containing Bitcoin funds to increase liquidity for the pool’s position and cover the transaction fee
* **Input 6:** Trading wallet’s UTXO containing Rune token to increase liquidity for the pool’s position

**Outputs:**

* **Outputs 0-3:** New pool liquidity UTXOs
* **Output 4:** radFi `OP_12` script containing increase liquidity data:
  * **Byte 0 (Flag):** 5 (Increase liquidity flag)
  * **Remaining bytes:** Contain 11 `uvarint128` values:
    * **Min0:** 0
    * **Min1:** 0
    * **NftId:** 14
    * **Amount0:** 10000964745485757151
    * **Amount1:** 1000
    * **SequenceNumber:** 64
    * **Fee:** 5 (0.05%)
    * **Token0Id - Block:** 885548
    * **Token0Id - Tx:** 319
    * **Token1Id - Block:** 0
    * **Token1Id - Tx:** 0
* **Output 5:** `Runestone OP_RETURN` containing data of liquidity in the new UTXOs and Rune change for the trading wallet
* **Output 6:** Trading wallet’s Rune change UTXO
* **Output 7:** Trading wallet’s Bitcoin change UTXO

## 7. Migrate pool to new address

**Inputs:**

* **Inputs 0-9:** pool liquidity UTXOs from old address
* **Remain inputs:** admin wallet’s bitcoin UTXOs to pay transaction fee

**Outputs:**

* **Outputs 0-9:** new pool liquidity UTXOs for new address
* **Output 10:** radFi OP\_12 outputs contain pool migration data
* **Output 11:** runestone OP\_RETURN
* **Output 12:** admin wallet’s bitcoin change

**Example:** <https://mempool.space/tx/c269695a85fbbe672b46ece887fcb6753f9c6647541b6ef28d0161f0673164d8>

**Inputs:**

* **Inputs 0-9:** pool liquidity UTXOs from old address
* **Inputs 10-11:** admin wallet’s UTXOs contain bitcoin fund to cover the transaction fee

**Outputs:**

* **Outputs 0-9:** new pool liquidity UTXOs for new address
* **Output 10:** radFi OP\_12 script contain pool migration data:
  * **Byte 0 is Flag:** 7 (migrate pool flag)
  * **The remain bytes:** contain 5 uvarint128
    * **Fee:** 100 (mean 1%)
    * **Token0Id - Block:** 0
    * **Token0Id - Tx:** 0
    * **Token1Id - Block:** 867080
    * **Token1Id - Tx:** 468
* **Output 11:** runestone `OP_RETURN` contain data of liquidity in the new UTXOs

## 8. Migrate pool init UTXO to new address&#x20;

**Inputs:**

* **Input 0:** pool init UTXO from old address
* **Remain inputs:** admin wallet’s bitcoin UTXOs to pay transaction fee

**Outputs:**

* **Output 0:** new pool liquidity UTXOs for new address
* **Outputs 1:** radFi OP\_12 outputs contain pool migration data
* **Output 2:** runestone OP\_RETURN
* **Output 3:** admin wallet’s bitcoin change

**Example:** <https://mempool.space/tx/8962cecd385ba3c6ac6bc30634c4b233f925a580c0c5d2a47867c8986ef1609e>

**Inputs:**

* **Input 0:** pool init UTXO from old address
* **Input 1:** admin wallet’s UTXO contain bitcoin fund to cover the transaction fee

**Outputs:**

* **Output 0:** new pool init UTXO for new address
* **Output 1:** radFi OP\_12 script contain pool init UTXO migration data:
  * **Byte 0 is Flag:** 7 (migrate pool flag)
  * **The remain bytes:** contain 5 uvarint128
    * **Fee:** 0
    * **Token0Id - Block:** 0
    * **Token0Id - Tx:** 0
    * **Token1Id - Block:** 0
    * **Token1Id - Tx:** 0
* **Output 2:** admin wallet’s bitcoin change


# Trading Wallet

The trading wallet is implemented using **2-of-2 Timelocked Multisig** where

* two signatures are required to authorize a transaction&#x20;
* one of the signatures expires after a specific period

The trading wallet combines the user wallet’s private key with the radFi backend private key. This ensures that neither party can replace the user's transactions until the transaction has been confirmed on radFi.

<figure><img src="/files/1z1IgnFEGiCKZ9aQ7flD" alt=""><figcaption></figcaption></figure>

### **Key Components**

**User Private Key**

* The private key controlled by the user.
* Contributes to the multisig authentication required for the trading wallet.

**Back-end Private Key**

* A private key managed by the radFi backend system.
* Ensures platform-level security and coordination in wallet operations.

**Create Trading Wallet API**

* This API takes inputs from both the **user's private key** and the **backend private key**.
* It processes these keys to generate a **radFi multisig trading wallet**.

**radFi Multisign Trading Wallet**

* A **multisignature wallet** that requires both private keys for transaction execution.
* Enhances security by ensuring that both parties (user and radFi backend) must approve actions.

### **Flow Overview**

* The **user private key** and **backend private key** are sent to the **Create Trading Wallet API**.
* The API processes the request and generates a **radFi multisign trading wallet**.
* This wallet is then used for secure trading operations within the radFi ecosystem.


# FROST Multisig

### Overview

RadFi validators use **FROST (Flexible Round-Optimized Schnorr Threshold Signatures)** for decentralized signing of Bitcoin transactions.\
FROST replaces the legacy Taproot multisig scheme with a threshold signing protocol, increasing flexibility and resilience.

Key properties of this implementation:

* 5 signers (3-of-5 threshold)
* Distributed key generation (no single party ever holds the full private key)
* Signer key management in AWS Secrets Manager
* Support for key refresh, recovery, replacement, and removal

### Benefits

* Decentralized signing: No single point of failure.
* Security: Keys are distributed, refreshed on leak, and recoverable on loss.
* Flexibility: Signers can be replaced or removed without disrupting the wallet.
* Backward compatibility: Legacy Taproot key is retained for non-signing use cases

### Cryptographic Design

**Key Generation**

* A `pubkey_package` and 5 signer key pairs are generated.
* Each signer receives:
  * `secret` → backup seed
  * `key` → active signing key (derived from `secret`)

### Signing Model

* Threshold: **3-of-5**
* Signers collaborate via FROST rounds to produce a Schnorr signature.
* Signatures are valid Taproot-compatible signatures for Bitcoin transactions.

### Keys Lifecycle

**1. Refresh Keys**

* Used when a signer key leaks.
* Distributor issues refresh shares.
* Each signer derives a new key while retaining the same wallet identity.
* Old keys are destroyed.

**2. Recover a Lost Key**

* If one signer loses their key, any **3 helpers** can collaboratively reconstruct it.
* Recovery is a 3-step protocol using `delta` and `sigma` values.

**3. Replace a Key**

* A new signer can take over an old signer’s role by recovering their key, then refreshing.

**4. Remove a Key**

* 4 signers refresh without including the unwanted signer.


# Sequencer

Sequencers are nodes operated by RadFi and other stakeholders. They process raw Bitcoin transaction requests from clients, **validating sequence numbers**, **user signatures**, **data formats**, and **OPCODEs**. Once validated, the request is forwarded to other validator nodes.

Validators also **update the** **state** **on the** **EVM chain**, **sign the Bitcoin raw transaction**, and **share the signature with other validators**. When consensus is reached and all signatures are collected, one validator finalizes the BTC transaction and broadcasts it to the Bitcoin network.

<figure><img src="/files/F3yAr0Weyzro0Wgh53Ui" alt=""><figcaption><p>RadFi Validator Interaction</p></figcaption></figure>

### **Key Components**

**Client**

* Initiates the swap request.

**Sequencers**

* Handle request validation and relay information.

**EVM Node**

* Executes the swap and updates the blockchain state.

**Bitcoin Node**

* Signs and broadcasts the transaction to the Bitcoin network.

### **Flow Overview**

**1. Client Request**

* A user initiates a swap by submitting a BTC raw tx to a validator.

**2. Validation Process**

* Step 2: Sequencer 1 verifies the swap request.
* Step 2 (Rejection Case): If invalid, the request is rejected.
* Step 2 (Peer-to-Peer Validation): If necessary, the request is sent to other validators for consensus.

**3. Transaction Processing**

* The validated BTC raw transaction is parsed into an EVM-compatible swap data structure.
* A request is then sent to an EVM node to execute the swap and update the state.

**4. Bitcoin Network Broadcast**

* Once the swap is executed on the EVM node, a Bitcoin signature is collected.
* The signed Bitcoin transaction is broadcasted to the Bitcoin network via a Bitcoin node.


# Smart Contract

The smart contracts are deployed in an **EVM environment** and can only be called by Sequencers.

<figure><img src="/files/YXgoeoIcf4FCtHndBm0r" alt=""><figcaption></figcaption></figure>

### **Key Components**

**Relay Contract**

* The relay contract is responsible for handling sequencer requests.
* It stores these requests separately and ensures that consensus is reached.
* Once consensus is achieved, the relay contract forwards the request to the Bitcoinstate contract.

**Bitcoinstate Contract**

* This contract tracks user data, specifically the **liquidity provider (LP) positions** of each user.
* It parses the request data into a **Uniswap-compatible format**.
* The contract then interacts with Uniswap V3 by calling the appropriate functions and handling the response.

**Uniswap V3 Contract**

* The final contract in the flow is the Uniswap V3 contract, which processes the request sent from the Bitcoinstate contract.
* It executes the necessary actions related to liquidity provisioning or trading.

The contract is the core of the Sequencer node, responsible for validating and storing protocol state. Below is the `calldata` structure for the **Relay contract**.

```solidity
struct CSMessageRequestV2 {
    string from; // bitcoin address
    string to; // bitcoin state contract address
    uint256 sn; // sequence number
    int messageType; // message type
    bytes data; // uniswap v3 calldata
    string[] protocols; // validator addresses 
}

```

The contract is triggered at step (3) in [**RadFi Sequencer Interaction**](/sequencer), which will verify and update the contract state based on a user request.

### **Flow Overview**

The **Relay Contract** forwards requests → **Bitcoinstate Contract** processes & formats data → Calls **Uniswap V3 Contract** for execution.

This setup ensures a structured, validated, and efficient interaction between Bitcoin state tracking and Uniswap V3 liquidity operations.


# radFi Actions Flowchart

<figure><img src="/files/QIzke7e8G8TXQ2gZ2ESm" alt=""><figcaption></figcaption></figure>

### **Key Components**

**Frontend (FE)**&#x20;

* User interface for sending actions and signing transactions.

**RadFi Backend (BE)**&#x20;

* Processes requests, generates PSBTs, and handles transaction broadcasting.

**Pending State Contract**

* Temporary contract validating transactions before final submission.

**Sequencer**

* Ensures transaction validity before sending it to the Main State Contract.

**Main State Contract**

* Stores finalized transactions after validation.

**Bitcoin Chain**

* Bitcoin mainnet where transactions are permanently recorded on-chain.

**Database (DB)**

* Stores transaction-related data for tracking and verification.

### **Flow Overview**

**1. Fetch Pool and Ratio Data**

* The FE retrieves pool and ratio data from the user.

**2. User Action Request**

* The user initiates an action via the FE, which sends a request to the RadFi BE.

**3. Generate PSBT (Partially Signed Bitcoin Transaction)**

* The BE creates a PSBT and sends it back to the FE for user signing.

**4. User Signs PSBT**

* The user signs the PSBT and returns it to the BE.

**5. Broadcast Signed Transaction to Pending State Contract**

* The BE broadcasts the signed transaction to the Pending State Contract.

**6. Transaction Validation**

* The Pending State Contract processes the transaction and returns a success/failure status.

**7. Notify Frontend**

* The BE relays the transaction success/failure result back to the FE.

**8. Sequencer Processing**

* If the transaction is successful, the BE sends the signed transaction to the Sequencer for final verification.

**9. Broadcast Final Transaction to Main State Contract**

* The Validator broadcasts the final transaction to the Main State Contract.

**10. Transaction Finalization on Bitcoin Mainnet**

* The Validator broadcasts the fully signed transaction to Bitcoin mainnet, completing the process.


# API Endpoints


# Create Trading Wallet

### **Create Trading Wallet**

**Endpoint**: `/api/wallets`\
**Method**: `POST`

**Request Body**

```json
{
  "walletAddress": "string",
  "publicKey": "string"
}
```

**Response**

```json
{
  "code": 1,
  "message": "common.success",
  "data": {
    "tradingWallet": "string",
    "userAddress": "string",
    "userPublicKey": "string",
    "requiredSignNumber": "number"
  }
}
```


# Swap

### **Build PSBT**

**Endpoint:** `/api/transactions`\
**Method:** `POST`

**Request Body**

```json
{
  "type": "swap",
  "params": {
    "userAddress": "string",
    "tokens": ["string"],
    "amountInt": "string",
    "amountOut": "string",
    "feeRates": ["number"],
    "isExactIn": "boolean"
  }
}
```

**Response**

```json
{
  "code": 1,
  "message": "common.success",
  "data": {
    "base64Psbt": "string",
    "fee": {
      "feeRate": "number",
      "totalFee": "number"
    }
  }
}
```

### Sign and Broadcast Transaction

**Endpoint:** `/api/transactions/sign`\
**Method:** `POST`

**Request Body**

```json
{
  "type": "swap",
  "params": {
    "userAddress": "string",
    "signedBase64Tx": "string"
  }
}
```

**Response**

```json
{
  "code": 1,
  "message": "common.success",
  "data": {
    "txId": "string"
  }
}
```


# AMM


# Init Liquidity

### Build PSBT

**Endpoint:** `/api/transactions`\
**Method:** `POST`

**Request Body**

```json
{
  "type": "init-liquidity-pool",
  "params": {
    "userAddress": "string",
    "token0Id": "string",
    "token1Id": "string",
    "amount0": "string",
    "amount1": "string",
    "upperTick": "string",
    "lowerTick": "string",
    "feeRate": "number"
  }
}
```

**Response**

```json
{
  "code": 1,
  "message": "common.success",
  "data": {
    "base64Psbt": "string",
    "fee": {
      "feeRate": "number",
      "totalFee": "number"
    }
  }
}
```

### Sign and Broadcast Transaction

**Endpoint:** `/api/transactions/sign`\
**Method:** `POST`

**Request Body**

```json
{
  "type": "init-liquidity-pool",
  "params": {
    "userAddress": "string",
    "signedBase64Tx": "string"
  }
}
```

**Response**

```json
{
  "code": 1,
  "message": "common.success",
  "data": {
    "txId": "string"
  }
}
```


# Supply Liquidity

### Build PSBT

**Endpoint:** `/api/transactions`\
**Method:** `POST`

**Request Body**

```json
{
  "type": "provide-liquidity",
  "params": {
    "userAddress": "string",
    "token0Id": "string",
    "token1Id": "string",
    "amount0": "string",
    "amount1": "string",
    "upperTick": "string",
    "lowerTick": "string",
    "feeRate": "number"
  }
}
```

**Response**

```json
{
  "code": 1,
  "message": "common.success",
  "data": {
    "base64Psbt": "string",
    "fee": {
      "feeRate": "number",
      "totalFee": "number"
    }
  }
}
```

### Sign and Broadcast Transaction

**Endpoint:** `/api/transactions/sign`\
**Method:** `POST`

**Request Body**

```json
{
  "type": "provide-liquidity",
  "params": {
    "userAddress": "string",
    "signedBase64Tx": "string"
  }
}
```

**Response**

```json
{
  "code": 1,
  "message": "common.success",
  "data": {
    "txId": "string"
  }
}
```


# Increase Liquidity

### Build PSBT

**Endpoint:** `/api/transactions`\
**Method:** `POST`

**Request Body**

```json
{
  "type": "increase-liquidity",
  "params": {
    "userAddress": "string",
    "token0Id": "string",
    "token1Id": "string",
    "amount0": "string",
    "amount1": "string",
    "nftId": "string"
  }
}
```

**Response**

```json
{
  "code": 1,
  "message": "common.success",
  "data": {
    "base64Psbt": "string",
    "fee": {
      "feeRate": "number",
      "totalFee": "number"
    }
  }
}
```

### Sign and Broadcast Transaction

**Endpoint:** `/api/transactions/sign`\
**Method:** `POST`

**Request Body**

```json
{
  "type": "increase-liquidity",
  "params": {
    "userAddress": "string",
    "signedBase64Tx": "string"
  }
}
```

**Response**

```json
{
  "code": 1,
  "message": "common.success",
  "data": {
    "txId": "string"
  }
}
```


# Withdraw Liquidity

### Build PSBT

**Endpoint:** `/api/transactions`\
**Method:** `POST`

**Request Body**

```json
{
  "type": "withdraw-liquidity",
  "params": {
    "userAddress": "string",
    "token0Id": "string",
    "token1Id": "string",
    "amount0": "string",
    "amount1": "string",
    "liquidityValue": "string",
    "nftId": "string"
  }
}
```

**Response**

```json
{
  "code": 1,
  "message": "common.success",
  "data": {
    "base64Psbt": "string",
    "fee": {
      "feeRate": "number",
      "totalFee": "number"
    }
  }
}
```

### Sign and Broadcast Transaction

**Endpoint:** `/api/transactions/sign`\
**Method:** `POST`

**Request Body**

```json
{
  "type": "withdraw-liquidity",
  "params": {
    "userAddress": "string",
    "signedBase64Tx": "string"
  }
}
```

**Response**

```json
{
  "code": 1,
  "message": "common.success",
  "data": {
    "txId": "string"
  }
}
```


# Collect Fees

### Build PSBT

**Endpoint:** `/api/transactions`\
**Method:** `POST`

**Request Body**

```json
{
  "type": "collect-fee",
  "params": {
    "userAddress": "string",
    "token0Id": "string",
    "token1Id": "string",
    "amount0": "string",
    "amount1": "string",
    "liquidityValue": "string",
    "nftId": "string"
  }
}
```

**Response**

```json
{
  "code": 1,
  "message": "common.success",
  "data": {
    "base64Psbt": "string",
    "fee": {
      "feeRate": "number",
      "totalFee": "number"
    }
  }
}
```

### Sign and Broadcast Transaction

**Endpoint:** `/api/transactions/sign`\
**Method:** `POST`

**Request Body**

```json
{
  "type": "collect-fee",
  "params": {
    "userAddress": "string",
    "signedBase64Tx": "string"
  }
}
```

**Response**

```json
{
  "code": 1,
  "message": "common.success",
  "data": {
    "txId": "string"
  }
}
```


# Transactions

## POST /api/transactions

> Create transaction

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"CreateTransactionDto":{"type":"object","properties":{"type":{"type":"string","enum":["renew-utxo","withdraw","init-liquidity-pool","provide-liquidity","increase-liquidity","swap","withdraw-liquidity","collect-fee","claim-referral-rewards","claim-receipt","satflow-list","satflow-cancel","satflow-buy"]},"params":{"type":"object"}},"required":["type","params"]}}},"paths":{"/api/transactions":{"post":{"operationId":"TransactionController_create","summary":"Create transaction","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTransactionDto"}}}},"responses":{"201":{"description":""}},"tags":["transactions"]}}}}
```

## POST /api/transactions/sign

> Sign withdraw transaction

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"SignTransactionDto":{"type":"object","properties":{"type":{"type":"string","enum":["renew-utxo","withdraw","init-liquidity-pool","provide-liquidity","increase-liquidity","swap","withdraw-liquidity","collect-fee","claim-referral-rewards","claim-receipt","satflow-list","satflow-cancel","satflow-buy"]},"params":{"$ref":"#/components/schemas/SignTransactionParamsDto"}},"required":["type","params"]},"SignTransactionParamsDto":{"type":"object","properties":{"userAddress":{"type":"string"},"signedBase64Tx":{"type":"string"}},"required":["userAddress","signedBase64Tx"]},"BaseResponseDto":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"data":{"type":"object"},"metaData":{"$ref":"#/components/schemas/ResponseMetaData"}},"required":["code","message","data","metaData"]},"ResponseMetaData":{"type":"object","properties":{"totalItems":{"type":"number"},"currentPage":{"type":"number"},"pageSize":{"type":"number"},"totalPages":{"type":"number"}},"required":["totalItems","currentPage","pageSize","totalPages"]}}},"paths":{"/api/transactions/sign":{"post":{"operationId":"TransactionController_signAndBroadcast","summary":"Sign withdraw transaction","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignTransactionDto"}}}},"responses":{"201":{"description":"transaction has been successfully signed and broadcasted","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponseDto"}}}}},"tags":["transactions"]}}}}
```

## GET /api/transactions/mempool-fee

>

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/transactions/mempool-fee":{"get":{"operationId":"TransactionController_getMempoolFee","parameters":[],"responses":{"200":{"description":""}},"tags":["transactions"]}}}}
```

## POST /api/transactions/max-spent

>

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/transactions/max-spent":{"post":{"operationId":"TransactionController_getMaxSpent","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateTransactionDto"}}}},"responses":{"201":{"description":""}},"tags":["transactions"]}}},"components":{"schemas":{"CreateTransactionDto":{"type":"object","properties":{"type":{"type":"string","enum":["renew-utxo","withdraw","init-liquidity-pool","provide-liquidity","increase-liquidity","swap","withdraw-liquidity","collect-fee","claim-referral-rewards","claim-receipt","satflow-list","satflow-cancel","satflow-buy"]},"params":{"type":"object"}},"required":["type","params"]}}}}
```

## GET /api/transactions/receipt/{address}

>

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/transactions/receipt/{address}":{"get":{"operationId":"TransactionController_getReceipt","parameters":[{"name":"address","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["transactions"]}}}}
```


# Wallets

## GET /api/wallets

> Get paginated list of wallets

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/wallets":{"get":{"operationId":"WalletController_paginate","summary":"Get paginated list of wallets","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (default: 1)","schema":{"type":"number"}},{"name":"pageSize","required":false,"in":"query","description":"Number of items per page (default: 10)","schema":{"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Sort field and order. Use - prefix for descending. Example: -createdAt, createdAt","schema":{"type":"string"}},{"name":"select","required":false,"in":"query","description":"Fields to select (comma separated). Use + prefix to include hidden fields. Example: name,status,+holders","schema":{"type":"string"}},{"name":"populate","required":false,"in":"query","description":"Relations to populate (comma separated). Example: wallet,token","schema":{"type":"string"}}],"responses":{"200":{"description":"Get wallets","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponseDto"}}}}},"tags":["wallets"]}}},"components":{"schemas":{"BaseResponseDto":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"data":{"type":"object"},"metaData":{"$ref":"#/components/schemas/ResponseMetaData"}},"required":["code","message","data","metaData"]},"ResponseMetaData":{"type":"object","properties":{"totalItems":{"type":"number"},"currentPage":{"type":"number"},"pageSize":{"type":"number"},"totalPages":{"type":"number"}},"required":["totalItems","currentPage","pageSize","totalPages"]}}}}
```

## POST /api/wallets

> Add a wallet and link it to the authenticated account

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/wallets":{"post":{"operationId":"WalletController_addWallet","summary":"Add a wallet and link it to the authenticated account","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AddWalletDto"}}}},"responses":{"201":{"description":"The wallet has been successfully added","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponseDto"}}}}},"tags":["wallets"]}}},"components":{"schemas":{"AddWalletDto":{"type":"object","properties":{"chain":{"type":"string","enum":["bitcoin","evm","solana"],"description":"Blockchain network of the wallet"},"provider":{"type":"string","description":"Wallet provider / software used"},"publicKey":{"type":"string","description":"Public key of the wallet"},"message":{"type":"string","description":"Message (timestamp in ms) signed by the wallet"},"signature":{"type":"string","description":"Signature of the message produced by the wallet"},"userAddress":{"type":"string","description":"Wallet address (required for BTC; derived from signature for EVM/SOL)"},"isDefault":{"type":"boolean","description":"Mark this wallet as the default for its chain"}},"required":["chain","provider","publicKey","message","signature"]},"BaseResponseDto":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"data":{"type":"object"},"metaData":{"$ref":"#/components/schemas/ResponseMetaData"}},"required":["code","message","data","metaData"]},"ResponseMetaData":{"type":"object","properties":{"totalItems":{"type":"number"},"currentPage":{"type":"number"},"pageSize":{"type":"number"},"totalPages":{"type":"number"}},"required":["totalItems","currentPage","pageSize","totalPages"]}}}}
```

## GET /api/wallets/details

>

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/wallets/details":{"get":{"operationId":"WalletController_details","parameters":[],"responses":{"200":{"description":"Get wallet details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponseDto"}}}}},"tags":["wallets"]}}},"components":{"schemas":{"BaseResponseDto":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"data":{"type":"object"},"metaData":{"$ref":"#/components/schemas/ResponseMetaData"}},"required":["code","message","data","metaData"]},"ResponseMetaData":{"type":"object","properties":{"totalItems":{"type":"number"},"currentPage":{"type":"number"},"pageSize":{"type":"number"},"totalPages":{"type":"number"}},"required":["totalItems","currentPage","pageSize","totalPages"]}}}}
```

## GET /api/wallets/details/{userAddress}

> Check wallet existed

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/wallets/details/{userAddress}":{"get":{"operationId":"WalletController_getDetails","summary":"Check wallet existed","parameters":[{"name":"userAddress","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"The wallet has been successfully checked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponseDto"}}}}},"tags":["wallets"]}}},"components":{"schemas":{"BaseResponseDto":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"data":{"type":"object"},"metaData":{"$ref":"#/components/schemas/ResponseMetaData"}},"required":["code","message","data","metaData"]},"ResponseMetaData":{"type":"object","properties":{"totalItems":{"type":"number"},"currentPage":{"type":"number"},"pageSize":{"type":"number"},"totalPages":{"type":"number"}},"required":["totalItems","currentPage","pageSize","totalPages"]}}}}
```

## PATCH /api/wallets/{id}

> Update a wallet by ID

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/wallets/{id}":{"patch":{"operationId":"WalletController_updateWalletById","summary":"Update a wallet by ID","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/UpdateWalletDto"}}}},"responses":{"200":{"description":"The wallet has been successfully updated","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponseDto"}}}}},"tags":["wallets"]}}},"components":{"schemas":{"UpdateWalletDto":{"type":"object","properties":{"isDefault":{"type":"boolean"}}},"BaseResponseDto":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"data":{"type":"object"},"metaData":{"$ref":"#/components/schemas/ResponseMetaData"}},"required":["code","message","data","metaData"]},"ResponseMetaData":{"type":"object","properties":{"totalItems":{"type":"number"},"currentPage":{"type":"number"},"pageSize":{"type":"number"},"totalPages":{"type":"number"}},"required":["totalItems","currentPage","pageSize","totalPages"]}}}}
```

## PUT /api/wallets/{id}/unlink

> Unlink a provider-connected wallet from the authenticated account

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/wallets/{id}/unlink":{"put":{"operationId":"WalletController_unlinkWallet","summary":"Unlink a provider-connected wallet from the authenticated account","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"The wallet has been successfully unlinked","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponseDto"}}}}},"tags":["wallets"]}}},"components":{"schemas":{"BaseResponseDto":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"data":{"type":"object"},"metaData":{"$ref":"#/components/schemas/ResponseMetaData"}},"required":["code","message","data","metaData"]},"ResponseMetaData":{"type":"object","properties":{"totalItems":{"type":"number"},"currentPage":{"type":"number"},"pageSize":{"type":"number"},"totalPages":{"type":"number"}},"required":["totalItems","currentPage","pageSize","totalPages"]}}}}
```


# Pools

## GET /api/pools

> Get pools

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/pools":{"get":{"operationId":"PoolController_getPools","summary":"Get pools","parameters":[],"responses":{"200":{"description":""}},"tags":["pools"]}}}}
```

## POST /api/pools/build-migration

> Migrate pools

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/pools/build-migration":{"post":{"operationId":"PoolController_migratePools","summary":"Migrate pools","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MigratePoolDto"}}}},"responses":{"201":{"description":""}},"tags":["pools"]}}},"components":{"schemas":{"MigratePoolDto":{"type":"object","properties":{"payFeeAddress":{"type":"string","description":"The address to pay the fee"},"pools":{"description":"List of pools to migrate, if not exist, will migrate all","type":"array","items":{"$ref":"#/components/schemas/PoolMigrationDto"}},"migrateVersion":{"type":"number","description":"The migration version"},"type":{"type":"string","description":"The additional data"},"feeRate":{"type":"number","description":"The fee rate"}},"required":["payFeeAddress","migrateVersion","type","feeRate"]},"PoolMigrationDto":{"type":"object","properties":{"token0Id":{"type":"string","description":"The id of the token0"},"token1Id":{"type":"string","description":"The id of the token1"},"fee":{"type":"number","description":"The fee of the pool"},"scVersion":{"type":"string","description":"The sc version"}},"required":["token0Id","token1Id","fee","scVersion"]}}}}
```

## POST /api/pools/simulate-migration

> Migrate pools

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/pools/simulate-migration":{"post":{"operationId":"PoolController_simulateMigration","summary":"Migrate pools","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MigratePoolDto"}}}},"responses":{"201":{"description":""}},"tags":["pools"]}}},"components":{"schemas":{"MigratePoolDto":{"type":"object","properties":{"payFeeAddress":{"type":"string","description":"The address to pay the fee"},"pools":{"description":"List of pools to migrate, if not exist, will migrate all","type":"array","items":{"$ref":"#/components/schemas/PoolMigrationDto"}},"migrateVersion":{"type":"number","description":"The migration version"},"type":{"type":"string","description":"The additional data"},"feeRate":{"type":"number","description":"The fee rate"}},"required":["payFeeAddress","migrateVersion","type","feeRate"]},"PoolMigrationDto":{"type":"object","properties":{"token0Id":{"type":"string","description":"The id of the token0"},"token1Id":{"type":"string","description":"The id of the token1"},"fee":{"type":"number","description":"The fee of the pool"},"scVersion":{"type":"string","description":"The sc version"}},"required":["token0Id","token1Id","fee","scVersion"]}}}}
```

## POST /api/pools/broadcast-migration

> Broadcast migration

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/pools/broadcast-migration":{"post":{"operationId":"PoolController_broadcastMigration","summary":"Broadcast migration","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BroadcastMigrationDto"}}}},"responses":{"201":{"description":""}},"tags":["pools"]}}},"components":{"schemas":{"BroadcastMigrationDto":{"type":"object","properties":{"txHex":{"type":"string","description":"The tx hex"},"type":{"type":"string","description":"The tx id"},"migrateVersion":{"type":"number","description":"The migration version"}},"required":["txHex","type","migrateVersion"]}}}}
```


# Tokens

## GET /api/tokens

> Get tokens

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/tokens":{"get":{"operationId":"TokenController_getTokens","summary":"Get tokens","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (default: 1)","schema":{"type":"number"}},{"name":"pageSize","required":false,"in":"query","description":"Number of items per page (default: 10)","schema":{"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Sort field and order. Use - prefix for descending. Example: -createdAt, createdAt","schema":{"type":"string"}},{"name":"select","required":false,"in":"query","description":"Fields to select (comma separated). Use + prefix to include hidden fields. Example: name,status,+holders","schema":{"type":"string"}},{"name":"populate","required":false,"in":"query","description":"Relations to populate (comma separated). Example: wallet,token","schema":{"type":"string"}}],"responses":{"200":{"description":"Get tokens","content":{"application/json":{"schema":{"$ref":"#/components/schemas/TokenResponseDto"}}}}},"tags":["tokens"]}}},"components":{"schemas":{"TokenResponseDto":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"data":{"type":"array","items":{"$ref":"#/components/schemas/TokenEntity"}},"metaData":{"$ref":"#/components/schemas/ResponseMetaData"}},"required":["code","message","data","metaData"]},"TokenEntity":{"type":"object","properties":{"source":{"type":"string"},"chainName":{"type":"string"},"chainId":{"type":"string"},"symbol":{"type":"string"},"rune":{"type":"string"},"spacedRune":{"type":"string"},"decimals":{"type":"number"},"tokenAddress":{"type":"string"},"mainTokenAddress":{"type":"string"},"tokenAddressV4":{"type":"string"},"mainTokenAddressV4":{"type":"string"},"tokenId":{"type":"string"},"price":{"type":"number"},"priceInSats":{"type":"number"},"priceChange24h":{"type":"number"},"isTest":{"type":"boolean"},"updatedBy":{"type":"string"},"displayTicker":{"type":"string"},"supply":{"type":"string"},"premine":{"type":"string"},"social":{"$ref":"#/components/schemas/TokenSocial"},"volume24h":{"type":"number"},"volume24hInSats":{"type":"number"},"volume7d":{"type":"number"},"volume7dInSats":{"type":"number"},"volumeAllTime":{"type":"number"},"volumeAllTimeInSats":{"type":"number"},"marketCap":{"type":"number"},"marketCapInSats":{"type":"number"},"hasPool":{"type":"boolean"}},"required":["source","chainName","chainId","symbol","rune","spacedRune","decimals","tokenAddress","mainTokenAddress","tokenAddressV4","mainTokenAddressV4","tokenId","price","priceInSats","priceChange24h","isTest","updatedBy","displayTicker","supply","premine","social","volume24h","volume24hInSats","volume7d","volume7dInSats","volumeAllTime","volumeAllTimeInSats","marketCap","marketCapInSats","hasPool"]},"TokenSocial":{"type":"object","properties":{}},"ResponseMetaData":{"type":"object","properties":{"totalItems":{"type":"number"},"currentPage":{"type":"number"},"pageSize":{"type":"number"},"totalPages":{"type":"number"}},"required":["totalItems","currentPage","pageSize","totalPages"]}}}}
```

## GET /api/tokens/details

> Get token details

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/tokens/details":{"get":{"operationId":"TokenController_getEtchedRune","summary":"Get token details","parameters":[{"name":"select","required":false,"in":"query","description":"Fields to select (comma separated). Use + prefix to include hidden fields. Example: name,status,+holders","schema":{"type":"string"}},{"name":"populate","required":false,"in":"query","description":"Relations to populate (comma separated). Example: wallet,token","schema":{"type":"string"}},{"name":"sort","required":false,"in":"query","description":"Sort field and order. Use - prefix for descending. Example: -createdAt","schema":{"type":"string"}}],"responses":{"200":{"description":"token details","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponseDto"}}}}},"tags":["tokens"]}}},"components":{"schemas":{"BaseResponseDto":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"data":{"type":"object"},"metaData":{"$ref":"#/components/schemas/ResponseMetaData"}},"required":["code","message","data","metaData"]},"ResponseMetaData":{"type":"object","properties":{"totalItems":{"type":"number"},"currentPage":{"type":"number"},"pageSize":{"type":"number"},"totalPages":{"type":"number"}},"required":["totalItems","currentPage","pageSize","totalPages"]}}}}
```


# Histories

## Get histories by address

> Returns paginated history items.\
> \
> \- Each swap has: token0Id, token1Id, token0Amount, token1Amount, feeTier, fee, volumeInSats etc.\
> \- tokenIn/tokenOut = the pair token0/token1 and amounts (direction comes from which side is in/out).\
> \- fee is the pool fee (e.g. fee 10000 = 1% tier).\
> \- If tokens\_eq is true, return tokens with history items.

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/histories":{"get":{"operationId":"HistoryController_paginate","summary":"Get histories by address","description":"Returns paginated history items.\n\n- Each swap has: token0Id, token1Id, token0Amount, token1Amount, feeTier, fee, volumeInSats etc.\n- tokenIn/tokenOut = the pair token0/token1 and amounts (direction comes from which side is in/out).\n- fee is the pool fee (e.g. fee 10000 = 1% tier).\n- If tokens_eq is true, return tokens with history items.","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (default: 1)","schema":{"type":"number"}},{"name":"pageSize","required":false,"in":"query","description":"Number of items per page (default: 10)","schema":{"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Sort field and order. Use - prefix for descending. Example: -createdAt, createdAt","schema":{"type":"string"}},{"name":"select","required":false,"in":"query","description":"Fields to select (comma separated). Use + prefix to include hidden fields. Example: name,status,+holders","schema":{"type":"string"}},{"name":"populate","required":false,"in":"query","description":"Relations to populate (comma separated). Example: wallet,token","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["histories"]}}}}
```


# Positions

## GET /api/positions

> Get positions

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/positions":{"get":{"operationId":"PositionController_paginate","summary":"Get positions","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (default: 1)","schema":{"type":"number"}},{"name":"pageSize","required":false,"in":"query","description":"Number of items per page (default: 10)","schema":{"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Sort field and order. Use - prefix for descending. Example: -createdAt, createdAt","schema":{"type":"string"}},{"name":"select","required":false,"in":"query","description":"Fields to select (comma separated). Use + prefix to include hidden fields. Example: name,status,+holders","schema":{"type":"string"}},{"name":"populate","required":false,"in":"query","description":"Relations to populate (comma separated). Example: wallet,token","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["positions"]}}}}
```


# Pnl

## Get PnL position for a trading address and token

> Returns the materialized cost basis position for chart overlay.\
> \
> \- source: amm | sodax\
> \- avgCostBasis: USD per display token unit\
> \- totalQty: remaining display token quantity\
> \- Returns zeroed fields when no position exists.

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/pnl/position":{"get":{"operationId":"PnlController_getPosition","summary":"Get PnL position for a trading address and token","description":"Returns the materialized cost basis position for chart overlay.\n\n- source: amm | sodax\n- avgCostBasis: USD per display token unit\n- totalQty: remaining display token quantity\n- Returns zeroed fields when no position exists.","parameters":[{"name":"source","required":true,"in":"query","schema":{"enum":["amm","sodax"],"type":"string"}},{"name":"tokenId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["pnl"]}}}}
```

## Get PnL balances for a trading address

> Returns all non-zero positions with live unrealized PnL for the Balances tab.\
> \
> \- AMM and SODAX positions are returned as separate items (source field).\
> \- unrealizedPnl = (currentPrice − avgCostBasis) × totalQty (USD)\
> \- Fully closed positions (totalQty = 0) are excluded.

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/pnl/balances":{"get":{"operationId":"PnlController_getPnlBalances","summary":"Get PnL balances for a trading address","description":"Returns all non-zero positions with live unrealized PnL for the Balances tab.\n\n- AMM and SODAX positions are returned as separate items (source field).\n- unrealizedPnl = (currentPrice − avgCostBasis) × totalQty (USD)\n- Fully closed positions (totalQty = 0) are excluded.","parameters":[],"responses":{"200":{"description":""}},"tags":["pnl"]}}}}
```


# Etch

## POST /api/etch/validate/etch-rune

> Validate rune name uniqueness & block-height availability

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/etch/validate/etch-rune":{"post":{"operationId":"EtchController_validateEtchRune","summary":"Validate rune name uniqueness & block-height availability","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ValidateEtchRuneDto"}}}},"responses":{"200":{"description":"result: already_etched | height_not_reach | ok (HTTP 503 when rune availability cannot be verified — all providers unreachable)","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponseDto"}}}}},"tags":["etch"]}}},"components":{"schemas":{"ValidateEtchRuneDto":{"type":"object","properties":{"rune":{"type":"string","description":"Rune name with or without spacers (•)"}},"required":["rune"]},"BaseResponseDto":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"data":{"type":"object"},"metaData":{"$ref":"#/components/schemas/ResponseMetaData"}},"required":["code","message","data","metaData"]},"ResponseMetaData":{"type":"object","properties":{"totalItems":{"type":"number"},"currentPage":{"type":"number"},"pageSize":{"type":"number"},"totalPages":{"type":"number"}},"required":["totalItems","currentPage","pageSize","totalPages"]}}}}
```

## POST /api/etch/get-etch-address

> Get etch address and fee

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/etch/get-etch-address":{"post":{"operationId":"EtchController_getEtchAddress","summary":"Get etch address and fee","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/FetchEtchAddressDto"}}}},"responses":{"200":{"description":"Success","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponseDto"}}}}},"tags":["etch"]}}},"components":{"schemas":{"FetchEtchAddressDto":{"type":"object","properties":{"runeName":{"type":"string"},"inscriptionType":{"type":"string"},"inscriptionContent":{"type":"string"}},"required":["runeName","inscriptionType","inscriptionContent"]},"BaseResponseDto":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"data":{"type":"object"},"metaData":{"$ref":"#/components/schemas/ResponseMetaData"}},"required":["code","message","data","metaData"]},"ResponseMetaData":{"type":"object","properties":{"totalItems":{"type":"number"},"currentPage":{"type":"number"},"pageSize":{"type":"number"},"totalPages":{"type":"number"}},"required":["totalItems","currentPage","pageSize","totalPages"]}}}}
```

## GET /api/etch/runes/details

> Get etch rune by id

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/etch/runes/details":{"get":{"operationId":"EtchController_getEtchedRune","summary":"Get etch rune by id","parameters":[{"name":"select","required":false,"in":"query","description":"Fields to select (comma separated). Use + prefix to include hidden fields. Example: name,status,+holders","schema":{"type":"string"}},{"name":"populate","required":false,"in":"query","description":"Relations to populate (comma separated). Example: wallet,token","schema":{"type":"string"}},{"name":"sort","required":false,"in":"query","description":"Sort field and order. Use - prefix for descending. Example: -createdAt","schema":{"type":"string"}}],"responses":{"200":{"description":"etch rune","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponseDto"}}}}},"tags":["etch"]}}},"components":{"schemas":{"BaseResponseDto":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"data":{"type":"object"},"metaData":{"$ref":"#/components/schemas/ResponseMetaData"}},"required":["code","message","data","metaData"]},"ResponseMetaData":{"type":"object","properties":{"totalItems":{"type":"number"},"currentPage":{"type":"number"},"pageSize":{"type":"number"},"totalPages":{"type":"number"}},"required":["totalItems","currentPage","pageSize","totalPages"]}}}}
```

## GET /api/etch/runes

> Get etched runes

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/etch/runes":{"get":{"operationId":"EtchController_getEtchedRunes","summary":"Get etched runes","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (default: 1)","schema":{"type":"number"}},{"name":"pageSize","required":false,"in":"query","description":"Number of items per page (default: 10)","schema":{"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Sort field and order. Use - prefix for descending. Example: -createdAt, createdAt","schema":{"type":"string"}},{"name":"select","required":false,"in":"query","description":"Fields to select (comma separated). Use + prefix to include hidden fields. Example: name,status,+holders","schema":{"type":"string"}},{"name":"populate","required":false,"in":"query","description":"Relations to populate (comma separated). Example: wallet,token","schema":{"type":"string"}}],"responses":{"200":{"description":"etched runes","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/BaseResponseDto"},{"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/EtchEntity"}}}}]}}}}},"tags":["etch"]}}},"components":{"schemas":{"BaseResponseDto":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"data":{"type":"object"},"metaData":{"$ref":"#/components/schemas/ResponseMetaData"}},"required":["code","message","data","metaData"]},"ResponseMetaData":{"type":"object","properties":{"totalItems":{"type":"number"},"currentPage":{"type":"number"},"pageSize":{"type":"number"},"totalPages":{"type":"number"}},"required":["totalItems","currentPage","pageSize","totalPages"]},"EtchEntity":{"type":"object","properties":{}}}}}
```

## POST /api/etch/commit-tx

>

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"EtchBuildCommitTxDto":{"type":"object","properties":{"runeName":{"type":"string"},"inscriptionType":{"type":"string"},"inscriptionContent":{"type":"string"},"userAddress":{"type":"string"},"feeTier":{"type":"number","description":"Fee tier for the pool (5000 = 0.5%, 10000 = 1%, 20000 = 2%, etc.)","minimum":5000,"maximum":100000},"diamondHandRewardRate":{"type":"number","description":"Diamond hands share of the available 66.67% (0-1, e.g., 0.5 = 50% of 66.67% = 33.33% total)","minimum":0,"maximum":1},"blockWithdrawals":{"type":"boolean","description":"Block withdrawals of this token from radFi (only internal transfers allowed)","default":false},"addLpSatsRate":{"type":"number","description":"Percentage of raised funds to allocate to liquidity (0-1, e.g., 0.2 = 20% to LP, 80% to creator)","minimum":0,"maximum":1},"customFeeRate":{"type":"number","description":"Customized fee rate for the etching transaction (>=1)","minimum":1},"isReserve":{"type":"boolean","description":"Reserve to launch the token at a later date","default":false},"userPremineAmount":{"type":"string","description":"Raw rune amount (base units) to transfer to the creator before public distribution. Must fit within the total premine supply."}},"required":["runeName","inscriptionType","inscriptionContent","userAddress","feeTier"]}}},"paths":{"/api/etch/commit-tx":{"post":{"operationId":"EtchController_buildCommitTx","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EtchBuildCommitTxDto"}}}},"responses":{"201":{"description":""}},"tags":["etch"]}}}}
```

## POST /api/etch/submit-etching

>

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"SubmitEtchDto":{"type":"object","properties":{"signedBase64Psbt":{"type":"string"},"runeName":{"type":"string"},"inscriptionType":{"type":"string"},"inscriptionContent":{"type":"string"},"symbol":{"type":"string"},"creator":{"type":"string"},"description":{"type":"string"},"displayTicker":{"type":"string"},"social":{"$ref":"#/components/schemas/EtchSocialDto"}},"required":["signedBase64Psbt","runeName","inscriptionType","inscriptionContent","symbol","creator","description"]},"EtchSocialDto":{"type":"object","properties":{"x":{"type":"string"},"telegram":{"type":"string"},"website":{"type":"string"}}}}},"paths":{"/api/etch/submit-etching":{"post":{"operationId":"EtchController_submitEtch","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SubmitEtchDto"}}}},"responses":{"201":{"description":""}},"tags":["etch"]}}}}
```

## GET /api/etch/top-holders

>

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/etch/top-holders":{"get":{"operationId":"EtchController_getTopHolders","parameters":[{"name":"runeId_eq","required":false,"in":"query","schema":{"type":"string"}},{"name":"balance_gte","required":false,"in":"query","schema":{"type":"number"}},{"name":"page","required":false,"in":"query","schema":{"type":"number"}},{"name":"pageSize","required":false,"in":"query","schema":{"type":"number"}}],"responses":{"200":{"description":""}},"tags":["etch"]}}}}
```

## POST /api/etch/build-get-reward-tx

>

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"EtchBuildGetRewardDto":{"type":"object","properties":{"runeId":{"type":"string"},"userAddress":{"type":"string"}},"required":["runeId","userAddress"]}}},"paths":{"/api/etch/build-get-reward-tx":{"post":{"operationId":"EtchController_buildGetRewardTx","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EtchBuildGetRewardDto"}}}},"responses":{"201":{"description":""}},"tags":["etch"]}}}}
```

## POST /api/etch/sign-reward-tx

>

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"EtchSignAndBroadcastRewardTxDto":{"type":"object","properties":{"signedBase64Tx":{"type":"string"},"userAddress":{"type":"string"}},"required":["signedBase64Tx","userAddress"]}}},"paths":{"/api/etch/sign-reward-tx":{"post":{"operationId":"EtchController_signRewardTx","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EtchSignAndBroadcastRewardTxDto"}}}},"responses":{"201":{"description":""}},"tags":["etch"]}}}}
```

## GET /api/etch/rewards-amount

>

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/etch/rewards-amount":{"get":{"operationId":"EtchController_getRewardsAmount","parameters":[{"name":"userAddress","required":true,"in":"query","schema":{"type":"string"}},{"name":"runeIds","required":true,"in":"query","description":"separated by comma","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["etch"]}}}}
```

## POST /api/etch/open-mint

>

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"EtchBuildOpenMintDto":{"type":"object","properties":{"runeId":{"type":"string"},"userAddress":{"type":"string"}},"required":["runeId"]}}},"paths":{"/api/etch/open-mint":{"post":{"operationId":"EtchController_openMint","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EtchBuildOpenMintDto"}}}},"responses":{"201":{"description":""}},"tags":["etch"]}}}}
```


# Vm Transactions

## GET /api/vm-transactions

> Get vm transaction

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/vm-transactions":{"get":{"operationId":"VMTransactionController_getVmTransaction","summary":"Get vm transaction","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (default: 1)","schema":{"type":"number"}},{"name":"pageSize","required":false,"in":"query","description":"Number of items per page (default: 10)","schema":{"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Sort field and order. Use - prefix for descending. Example: -createdAt, createdAt","schema":{"type":"string"}},{"name":"select","required":false,"in":"query","description":"Fields to select (comma separated). Use + prefix to include hidden fields. Example: name,status,+holders","schema":{"type":"string"}},{"name":"populate","required":false,"in":"query","description":"Relations to populate (comma separated). Example: wallet,token","schema":{"type":"string"}}],"responses":{"200":{"description":"vm transaction","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/BaseResponseDto"},{"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/VMTransactionEntity"}}}}]}}}}},"tags":["vm-transactions"]}}},"components":{"schemas":{"BaseResponseDto":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"data":{"type":"object"},"metaData":{"$ref":"#/components/schemas/ResponseMetaData"}},"required":["code","message","data","metaData"]},"ResponseMetaData":{"type":"object","properties":{"totalItems":{"type":"number"},"currentPage":{"type":"number"},"pageSize":{"type":"number"},"totalPages":{"type":"number"}},"required":["totalItems","currentPage","pageSize","totalPages"]},"VMTransactionEntity":{"type":"object","properties":{"txId":{"type":"string"},"replaceTxId":{"type":"string"},"state":{"type":"string"},"runeId":{"type":"string"},"replacedRequestsCount":{"type":"number"},"requestsInMempoolCount":{"type":"number"},"requestsInBlocksCount":{"type":"number"},"satsPerNewRequest":{"type":"number"},"satsNewRequests":{"type":"number"},"satsPreviousRequests":{"type":"number"},"tradingAddress":{"type":"string"},"base64Tx":{"type":"string"},"timestamp":{"type":"number"},"transferBtcTxId":{"type":"string"},"transferBtcAt":{"type":"number"},"distributeRuneTxId":{"type":"string"},"distributeRuneAt":{"type":"number"},"refundTxId":{"type":"string"},"isRefundable":{"type":"boolean"},"vmDistributionId":{"type":"string"}},"required":["txId","replaceTxId","state","runeId","replacedRequestsCount","requestsInMempoolCount","requestsInBlocksCount","satsPerNewRequest","satsNewRequests","satsPreviousRequests","tradingAddress","base64Tx","timestamp","transferBtcTxId","transferBtcAt","distributeRuneTxId","distributeRuneAt","refundTxId","isRefundable","vmDistributionId"]}}}}
```

## POST /api/vm-transactions

> Create VM transaction

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"MintDto":{"type":"object","properties":{"userAddress":{"type":"string"},"runeId":{"type":"string"},"requestCount":{"type":"number"},"satsNewRequests":{"type":"number"}},"required":["userAddress","runeId","requestCount","satsNewRequests"]}}},"paths":{"/api/vm-transactions":{"post":{"operationId":"VMTransactionController_create","summary":"Create VM transaction","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/MintDto"}}}},"responses":{"201":{"description":""}},"tags":["vm-transactions"]}}}}
```

## POST /api/vm-transactions/sign

> Sign vm transaction

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"SignTransactionParamsDto":{"type":"object","properties":{"userAddress":{"type":"string"},"signedBase64Tx":{"type":"string"}},"required":["userAddress","signedBase64Tx"]},"BaseResponseDto":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"data":{"type":"object"},"metaData":{"$ref":"#/components/schemas/ResponseMetaData"}},"required":["code","message","data","metaData"]},"ResponseMetaData":{"type":"object","properties":{"totalItems":{"type":"number"},"currentPage":{"type":"number"},"pageSize":{"type":"number"},"totalPages":{"type":"number"}},"required":["totalItems","currentPage","pageSize","totalPages"]}}},"paths":{"/api/vm-transactions/sign":{"post":{"operationId":"VMTransactionController_signAndBroadcast","summary":"Sign vm transaction","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignTransactionParamsDto"}}}},"responses":{"201":{"description":"transaction has been successfully signed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponseDto"}}}}},"tags":["vm-transactions"]}}}}
```

## GET /api/vm-transactions/fee-rate

> Get fee rate

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/vm-transactions/fee-rate":{"get":{"operationId":"VMTransactionController_getFeeRate","summary":"Get fee rate","parameters":[],"responses":{"200":{"description":""}},"tags":["vm-transactions"]}}}}
```

## GET /api/vm-transactions/top-holders/{runeId}

> Get top holders by rune id

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/vm-transactions/top-holders/{runeId}":{"get":{"operationId":"VMTransactionController_getTopHolders","summary":"Get top holders by rune id","parameters":[{"name":"runeId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["vm-transactions"]}}}}
```

## GET /api/vm-transactions/confirmed-fee-rate/{runeId}

> Get fee rate by rune id

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/vm-transactions/confirmed-fee-rate/{runeId}":{"get":{"operationId":"VMTransactionController_getFeeRateByRuneId","summary":"Get fee rate by rune id","parameters":[{"name":"runeId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["vm-transactions"]}}}}
```

## GET /api/vm-transactions/count-unconfirmed-requests

>

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/vm-transactions/count-unconfirmed-requests":{"get":{"operationId":"VMTransactionController_countUnconfirmedRequests","parameters":[],"responses":{"200":{"description":""}},"tags":["vm-transactions"]}}}}
```


# Diamond Hand

## POST /api/diamond-hand/request-claim-rewards

> Claim diamond hand rewards

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"RequestClaimDiamondHandRewardsDto":{"type":"object","properties":{"userAddress":{"type":"string"},"runeId":{"type":"string"}},"required":["userAddress","runeId"]}}},"paths":{"/api/diamond-hand/request-claim-rewards":{"post":{"operationId":"DiamondHandController_claimDiamondHandRewards","summary":"Claim diamond hand rewards","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestClaimDiamondHandRewardsDto"}}}},"responses":{"201":{"description":""}},"tags":["diamond-hand"]}}}}
```

## POST /api/diamond-hand/sign

> Sign and broadcast diamond hand rewards

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"SignAndBroadcastRewardsDto":{"type":"object","properties":{"signedBase64Tx":{"type":"string"},"userAddress":{"type":"string"},"runeId":{"type":"string"}},"required":["signedBase64Tx","userAddress","runeId"]}}},"paths":{"/api/diamond-hand/sign":{"post":{"operationId":"DiamondHandController_signAndBroadcastRewards","summary":"Sign and broadcast diamond hand rewards","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignAndBroadcastRewardsDto"}}}},"responses":{"201":{"description":""}},"tags":["diamond-hand"]}}}}
```

## GET /api/diamond-hand/wallet-rewards

> Get wallet of diamond hand rewards

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/diamond-hand/wallet-rewards":{"get":{"operationId":"DiamondHandController_getWalletRewards","summary":"Get wallet of diamond hand rewards","parameters":[],"responses":{"200":{"description":""}},"tags":["diamond-hand"]}}}}
```

## GET /api/diamond-hand/list

> Get diamond hand list

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/diamond-hand/list":{"get":{"operationId":"DiamondHandController_getDiamondHandList","summary":"Get diamond hand list","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (default: 1)","schema":{"type":"number"}},{"name":"pageSize","required":false,"in":"query","description":"Number of items per page (default: 10)","schema":{"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Sort field and order. Use - prefix for descending. Example: -createdAt, createdAt","schema":{"type":"string"}},{"name":"select","required":false,"in":"query","description":"Fields to select (comma separated). Use + prefix to include hidden fields. Example: name,status,+holders","schema":{"type":"string"}},{"name":"populate","required":false,"in":"query","description":"Relations to populate (comma separated). Example: wallet,token","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["diamond-hand"]}}}}
```

## GET /api/diamond-hand/all-users

> Get all diamond hand users

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/diamond-hand/all-users":{"get":{"operationId":"DiamondHandController_getAllUsers","summary":"Get all diamond hand users","parameters":[{"name":"runeId","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["diamond-hand"]}}}}
```

## POST /api/diamond-hand/claim-locked-lp-rewards/{runeId}

> Claim locked LP rewards

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}}},"paths":{"/api/diamond-hand/claim-locked-lp-rewards/{runeId}":{"post":{"operationId":"DiamondHandController_claimLockedLPRewards","summary":"Claim locked LP rewards","parameters":[{"name":"runeId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"201":{"description":""}},"tags":["diamond-hand"]}}}}
```

## GET /api/diamond-hand/get-contract-total-lp-fee/{runeId}

> Get contract total LP fee

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/diamond-hand/get-contract-total-lp-fee/{runeId}":{"get":{"operationId":"DiamondHandController_getContractTotalLPFee","summary":"Get contract total LP fee","parameters":[{"name":"runeId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["diamond-hand"]}}}}
```


# Referral

## GET /api/referral/me

> Caller's referral overview — \`referralCode\`, total-users-referred count, lifetime borrow + swap rewards earned, and claimable swap rewards. No mid-week accrual is exposed (stat-hiding invariant). FE composes the share link itself (its own URL + \`?ref=\<referralCode>\`).

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}}},"paths":{"/api/referral/me":{"get":{"operationId":"ReferralController_me","summary":"Caller's referral overview — `referralCode`, total-users-referred count, lifetime borrow + swap rewards earned, and claimable swap rewards. No mid-week accrual is exposed (stat-hiding invariant). FE composes the share link itself (its own URL + `?ref=<referralCode>`).","parameters":[],"responses":{"200":{"description":""}},"tags":["referral"]}}}}
```

## POST /api/referral/claim/build

> Build an unsigned PSBT to claim bUSD rewards

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"ClaimBuildDto":{"type":"object","properties":{"payFeeAddress":{"type":"string","description":"Referrer's 2-of-2 trading wallet address. Funds the claim tx (fee + dust) AND receives the claimed bUSD. Must be the calling referrer's own `tradingAddress` — the BE validates this against `walletService` before building."}},"required":["payFeeAddress"]}}},"paths":{"/api/referral/claim/build":{"post":{"operationId":"ReferralController_buildClaim","summary":"Build an unsigned PSBT to claim bUSD rewards","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimBuildDto"}}}},"responses":{"201":{"description":""}},"tags":["referral"]}}}}
```

## POST /api/referral/claim/sign

> Submit a signed referral bUSD-claim PSBT for broadcast

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"ClaimSignDto":{"type":"object","properties":{"signedBase64Tx":{"type":"string","description":"Base64-encoded PSBT signed by the referrer"}},"required":["signedBase64Tx"]}}},"paths":{"/api/referral/claim/sign":{"post":{"operationId":"ReferralController_signClaim","summary":"Submit a signed referral bUSD-claim PSBT for broadcast","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ClaimSignDto"}}}},"responses":{"201":{"description":""}},"tags":["referral"]}}}}
```

## GET /api/referral/users/{accountId}/referrer

> Get an account's referrer (public)

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/referral/users/{accountId}/referrer":{"get":{"operationId":"ReferralController_getReferrer","summary":"Get an account's referrer (public)","parameters":[{"name":"accountId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["referral"]}}}}
```


# Account

## GET /api/account

> Admin: list accounts (paginated)

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}}},"paths":{"/api/account":{"get":{"operationId":"AccountController_paginate","summary":"Admin: list accounts (paginated)","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (default: 1)","schema":{"type":"number"}},{"name":"pageSize","required":false,"in":"query","description":"Number of items per page (default: 10)","schema":{"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Sort field and order. Use - prefix for descending. Example: -createdAt, createdAt","schema":{"type":"string"}},{"name":"select","required":false,"in":"query","description":"Fields to select (comma separated). Use + prefix to include hidden fields. Example: name,status,+holders","schema":{"type":"string"}},{"name":"populate","required":false,"in":"query","description":"Relations to populate (comma separated). Example: wallet,token","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["account"]}}}}
```

## GET /api/account/me

>

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/account/me":{"get":{"operationId":"AccountController_getMe","parameters":[],"responses":{"200":{"description":""}},"tags":["account"]}}}}
```

## GET /api/account/referral-code/{code}/verify

> Verify a referral code is valid (public)

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/account/referral-code/{code}/verify":{"get":{"operationId":"AccountController_verifyReferralCode","summary":"Verify a referral code is valid (public)","parameters":[{"name":"code","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["account"]}}}}
```

## Change password step 1 — get current password challenge

> Returns sessionId, salt, and serverPublic (B) to begin SRP proof of the existing password.

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/account/srp/change-password/init":{"post":{"operationId":"AccountController_srpChangePasswordInit","summary":"Change password step 1 — get current password challenge","description":"Returns sessionId, salt, and serverPublic (B) to begin SRP proof of the existing password.","parameters":[],"responses":{"200":{"description":"Returns sessionId, salt, and serverPublic (B)"}},"tags":["account"]}}}}
```

## Change password step 2 — verify old password and set new credentials

> Client proves knowledge of the old password via M1, then submits new salt, verifier, and encrypted blob atomically.

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/account/srp/change-password/verify":{"post":{"operationId":"AccountController_srpChangePasswordVerify","summary":"Change password step 2 — verify old password and set new credentials","description":"Client proves knowledge of the old password via M1, then submits new salt, verifier, and encrypted blob atomically.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangePasswordDto"}}}},"responses":{"200":{"description":"Password changed — returns serverProof (M2) for client verification"}},"tags":["account"]}}},"components":{"schemas":{"ChangePasswordDto":{"type":"object","properties":{"sessionId":{"type":"string","description":"Session ID returned by POST /auth/srp/change-password/init"},"clientPublic":{"type":"string","description":"Client ephemeral public key A = g^a mod N (base64)"},"clientProof":{"type":"string","description":"Client proof M1 = H(H(N)⊕H(g) || H(I) || salt || A || B || K) (base64)"},"newSrpSalt":{"type":"string","description":"New SRP salt generated client-side (base64, 32 bytes)"},"newSrpVerifier":{"type":"string","description":"New SRP verifier v = g^x mod N (base64)"},"newEncryptedBlob":{"type":"string","description":"Keystore blob re-encrypted with new derived key (base64, max 13708 chars)"},"newPasswordHint":{"type":"string","description":"New password hint"}},"required":["sessionId","clientPublic","clientProof","newSrpSalt","newSrpVerifier","newEncryptedBlob"]}}}}
```

## Rotate auth wallet — atomically swap the keystore to a new external wallet

> Wallet-auth accounts only. Client re-encrypts the blob with the NEW wallet derivation key client-side; server verifies the NEW wallet control proof (fresh nonce) and atomically replaces encryptedBlob + auth-wallet fields. Old-wallet derivation signature is never sent.

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"RotateAuthWalletDto":{"type":"object","properties":{"address":{"type":"string","description":"Address of the NEW auth wallet"},"chain":{"type":"string","enum":["evm","solana","bitcoin"]},"publicKey":{"type":"string","description":"Public key of the NEW auth wallet (verifies EVM/Solana signatures)"},"provider":{"type":"string","description":"metamask | phantom | xverse | okx | unisat"},"challengeId":{"type":"string","description":"challengeId from GET /auth/wallet/challenge?address=<newAddress>"},"signature":{"type":"string","description":"NEW wallet signature over the issued server nonce (control proof)"},"encryptedBlob":{"type":"string","description":"Keystore blob re-encrypted client-side with the NEW wallet derivation key"}},"required":["address","chain","publicKey","provider","challengeId","signature","encryptedBlob"]}}},"paths":{"/api/account/wallet/rotate":{"post":{"operationId":"AccountController_rotateAuthWallet","summary":"Rotate auth wallet — atomically swap the keystore to a new external wallet","description":"Wallet-auth accounts only. Client re-encrypts the blob with the NEW wallet derivation key client-side; server verifies the NEW wallet control proof (fresh nonce) and atomically replaces encryptedBlob + auth-wallet fields. Old-wallet derivation signature is never sent.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RotateAuthWalletDto"}}}},"responses":{"200":{"description":"Auth wallet rotated"},"400":{"description":"Method not allowed, signature invalid, challenge invalid, taken, or not a wallet keystore"},"401":{"description":"Unauthorized"}},"tags":["account"]}}}}
```

## POST /api/account/2fa/setup

> Set up TOTP 2FA — generate a new secret and return the otpauth URI

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/account/2fa/setup":{"post":{"operationId":"AccountController_setupTotp","summary":"Set up TOTP 2FA — generate a new secret and return the otpauth URI","parameters":[],"responses":{"200":{"description":"Returns otpauthUri for QR code rendering"}},"tags":["account"]}}}}
```

## POST /api/account/2fa/toggle

> Enable or disable TOTP 2FA — requires a valid OTP code

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/account/2fa/toggle":{"post":{"operationId":"AccountController_toggleTotp","summary":"Enable or disable TOTP 2FA — requires a valid OTP code","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TotpToggleDto"}}}},"responses":{"200":{"description":"2FA state toggled"},"400":{"description":"Invalid OTP code or setup not completed"}},"tags":["account"]}}},"components":{"schemas":{"TotpToggleDto":{"type":"object","properties":{}}}}}
```

## PATCH /api/account/avatar

> Set an inscription as the account avatar

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/account/avatar":{"patch":{"operationId":"AccountController_setAvatar","summary":"Set an inscription as the account avatar","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetAvatarDto"}}}},"responses":{"200":{"description":"Avatar updated"},"400":{"description":"Inscription not found or not owned by caller"}},"tags":["account"]}}},"components":{"schemas":{"SetAvatarDto":{"type":"object","properties":{"inscriptionId":{"type":"string","description":"The inscriptionId to set as avatar. Pass null or omit to remove the avatar.","nullable":true}}}}}}
```

## DELETE /api/account/default-token

> Remove a default token for a slot

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/account/default-token":{"delete":{"operationId":"AccountController_removeDefaultToken","summary":"Remove a default token for a slot","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RemoveDefaultTokenDto"}}}},"responses":{"200":{"description":"Default token removed successfully"},"400":{"description":"Invalid slot or tokenId"},"401":{"description":"Unauthorized"}},"tags":["account"]}}},"components":{"schemas":{"RemoveDefaultTokenDto":{"type":"object","properties":{"slot":{"type":"string","enum":["usdc"]},"tokenId":{"type":"string"}},"required":["slot","tokenId"]}}}}
```

## PATCH /api/account/default-token

> Set a default token for a slot

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/account/default-token":{"patch":{"operationId":"AccountController_setDefaultToken","summary":"Set a default token for a slot","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SetDefaultTokenDto"}}}},"responses":{"200":{"description":"Default token set successfully"},"400":{"description":"Token not found, unsupported chain, or token invalid for slot"},"401":{"description":"Unauthorized"}},"tags":["account"]}}},"components":{"schemas":{"SetDefaultTokenDto":{"type":"object","properties":{"slot":{"type":"string","enum":["usdc"]},"tokenId":{"type":"string"}},"required":["slot","tokenId"]}}}}
```

## Initiate email change — send verification link to new address

> Queues a verification email to the new address and a notice to the current address. Returns \`{ srpSession }\` for SRP accounts or \`{ webauthnChallenge }\` for passkey accounts — only one field is present at a time.

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/account/change-email/init":{"post":{"operationId":"AccountController_changeEmailInit","summary":"Initiate email change — send verification link to new address","description":"Queues a verification email to the new address and a notice to the current address. Returns `{ srpSession }` for SRP accounts or `{ webauthnChallenge }` for passkey accounts — only one field is present at a time.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeEmailInitDto"}}}},"responses":{"200":{"description":"Returns srpSession (SRP accounts) or webauthnChallenge (passkey accounts)"},"400":{"description":"New email already taken or same as current email"},"429":{"description":"Rate limit exceeded"}},"tags":["account"]}}},"components":{"schemas":{"ChangeEmailInitDto":{"type":"object","properties":{"newEmail":{"type":"string","description":"New email address to associate with the account"}},"required":["newEmail"]}}}}
```

## Confirm email change using verification token

> Finalises the email update. SRP accounts: \`srpSessionId\`, \`clientPublic\`, \`clientProof\`, and \`newSrpVerifier\` are conditionally required. \`srpSession\` from the init response provides \`sessionId\`, \`salt\`, and \`serverPublic\`. Passkey accounts: \`credentialId\`, \`challengeId\`, and \`webauthnAssertion\` are conditionally required. \`webauthnChallenge\` from the init response provides \`challengeId\` and \`challenge\`.

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/account/change-email/verify":{"post":{"operationId":"AccountController_changeEmailVerify","summary":"Confirm email change using verification token","description":"Finalises the email update. SRP accounts: `srpSessionId`, `clientPublic`, `clientProof`, and `newSrpVerifier` are conditionally required. `srpSession` from the init response provides `sessionId`, `salt`, and `serverPublic`. Passkey accounts: `credentialId`, `challengeId`, and `webauthnAssertion` are conditionally required. `webauthnChallenge` from the init response provides `challengeId` and `challenge`.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ChangeEmailVerifyDto"}}}},"responses":{"200":{"description":"Email updated successfully"},"400":{"description":"Invalid or expired token, email mismatch, or missing re-auth fields"}},"tags":["account"]}}},"components":{"schemas":{"ChangeEmailVerifyDto":{"type":"object","properties":{"token":{"type":"string","description":"Verification token from the change-email email link"},"email":{"type":"string","description":"New email address being verified (cross-verified against the token)"},"srpData":{"description":"SRP re-auth data — required when the account has an SRP keystore","allOf":[{"$ref":"#/components/schemas/ChangeEmailSrpDataDto"}]},"passkeyData":{"description":"Passkey re-auth data — required when the account has a passkey keystore","allOf":[{"$ref":"#/components/schemas/ChangeEmailPasskeyDataDto"}]}},"required":["token","email"]},"ChangeEmailSrpDataDto":{"type":"object","properties":{"newSrpVerifier":{"type":"string","description":"New SRP verifier recomputed as SRP(newEmail, currentPassword)"},"sessionId":{"type":"string","description":"SRP session ID from the init response"},"clientPublic":{"type":"string","description":"SRP client public ephemeral"},"clientProof":{"type":"string","description":"SRP client proof"}},"required":["newSrpVerifier","sessionId","clientPublic","clientProof"]},"ChangeEmailPasskeyDataDto":{"type":"object","properties":{"credentialId":{"type":"string","description":"Credential ID of the registered passkey"},"challengeId":{"type":"string","description":"Challenge ID from the init response"},"webauthnAssertion":{"description":"WebAuthn assertion from the passkey device","allOf":[{"$ref":"#/components/schemas/ChangeEmailWebAuthnAssertionDto"}]}},"required":["credentialId","challengeId","webauthnAssertion"]},"ChangeEmailWebAuthnAssertionDto":{"type":"object","properties":{"authenticatorData":{"type":"string","description":"Base64-encoded authenticator data"},"clientDataJson":{"type":"string","description":"Base64-encoded client data JSON"},"signature":{"type":"string","description":"Base64-encoded DER signature"}},"required":["authenticatorData","clientDataJson","signature"]}}}}
```


# Keystore

## GET /api/keystore

> List passkey keystores — self for users, all for admin/super\_admin

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}}},"paths":{"/api/keystore":{"get":{"operationId":"KeystoreController_paginate","summary":"List passkey keystores — self for users, all for admin/super_admin","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (default: 1)","schema":{"type":"number"}},{"name":"pageSize","required":false,"in":"query","description":"Number of items per page (default: 10)","schema":{"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Sort field and order. Use - prefix for descending. Example: -createdAt, createdAt","schema":{"type":"string"}},{"name":"select","required":false,"in":"query","description":"Fields to select (comma separated). Use + prefix to include hidden fields. Example: name,status,+holders","schema":{"type":"string"}},{"name":"populate","required":false,"in":"query","description":"Relations to populate (comma separated). Example: wallet,token","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["keystore"]}}}}
```

## GET /api/keystore/passkey/{email}/credentialIds

>

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/keystore/passkey/{email}/credentialIds":{"get":{"operationId":"KeystoreController_getCredentialIds","parameters":[{"name":"email","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["keystore"]}}}}
```

## POST /api/keystore/passkey/request-link

> Device B initiates ECDH passkey sync — returns OTP session

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/keystore/passkey/request-link":{"post":{"operationId":"KeystoreController_requestLink","summary":"Device B initiates ECDH passkey sync — returns OTP session","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestLinkDto"}}}},"responses":{"201":{"description":""}},"tags":["keystore"]}}},"components":{"schemas":{"RequestLinkDto":{"type":"object","properties":{"email":{"type":"string","description":"Email of the account Device B wants to link to"},"pubKeyB":{"type":"string","description":"Device B ephemeral ECDH public key (base64 or hex)"}},"required":["email","pubKeyB"]}}}}
```

## GET /api/keystore/passkey/pending-link

> Device A polls for Device B public key to complete ECDH

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/keystore/passkey/pending-link":{"get":{"operationId":"KeystoreController_getPendingLink","summary":"Device A polls for Device B public key to complete ECDH","parameters":[{"name":"otp","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["keystore"]}}}}
```

## POST /api/keystore/passkey/approve-link

> Device A stores ECDH-encrypted keystore blob for Device B

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/keystore/passkey/approve-link":{"post":{"operationId":"KeystoreController_approveLink","summary":"Device A stores ECDH-encrypted keystore blob for Device B","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ApproveLinkDto"}}}},"responses":{"201":{"description":""}},"tags":["keystore"]}}},"components":{"schemas":{"ApproveLinkDto":{"type":"object","properties":{"otp":{"type":"string","description":"OTP from the request-link session"},"encryptedForB":{"type":"string","description":"AES-GCM encrypted mnemonic, key derived via ECDH (base64)"},"pubKeyA":{"type":"string","description":"Device A ephemeral ECDH public key (base64 or hex)"}},"required":["otp","encryptedForB","pubKeyA"]}}}}
```

## GET /api/keystore/passkey/relay

> Device B polls for the ECDH-encrypted keystore blob from Device A

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/keystore/passkey/relay":{"get":{"operationId":"KeystoreController_getRelay","summary":"Device B polls for the ECDH-encrypted keystore blob from Device A","parameters":[{"name":"otp","required":true,"in":"query","description":"OTP from the request-link session","schema":{"type":"string"}},{"name":"email","required":true,"in":"query","description":"Email address — used to resolve the account for the relay key","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["keystore"]}}}}
```

## POST /api/keystore/passkey/confirm-link

> Device B registers its passkey and saves the decrypted keystore blob

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/keystore/passkey/confirm-link":{"post":{"operationId":"KeystoreController_confirmLink","summary":"Device B registers its passkey and saves the decrypted keystore blob","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/ConfirmLinkDto"}}}},"responses":{"201":{"description":""}},"tags":["keystore"]}}},"components":{"schemas":{"ConfirmLinkDto":{"type":"object","properties":{"otp":{"type":"string","description":"OTP from the request-link session"},"email":{"type":"string","description":"Email address — must match the account that owns the OTP session"},"challengeId":{"type":"string","description":"Challenge ID from GET /auth/webauthn/challenge"},"credentialId":{"type":"string","description":"WebAuthn credential ID"},"webauthnPublicKey":{"type":"string","description":"DER/SPKI public key (base64)"},"attestation":{"type":"string","description":"CBOR attestation object (base64)"},"clientDataJson":{"type":"string","description":"WebAuthn clientDataJSON (base64)"},"authenticatorData":{"type":"string","description":"WebAuthn authenticatorData (base64)"},"encryptedBlob":{"type":"string","description":"Keystore blob encrypted with Device B derived key"},"deviceName":{"type":"string","description":"Human-readable device label (e.g. \"Work Laptop\")"}},"required":["otp","email","challengeId","credentialId","webauthnPublicKey","attestation","clientDataJson","authenticatorData","encryptedBlob"]}}}}
```

## DELETE /api/keystore/passkey/{id}

> Remove a passkey by ID (blocked if it is the last one)

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/keystore/passkey/{id}":{"delete":{"operationId":"KeystoreController_removePasskey","summary":"Remove a passkey by ID (blocked if it is the last one)","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["keystore"]}}}}
```

## POST /api/keystore/srp/password-hint

> Send password hint to account email

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/keystore/srp/password-hint":{"post":{"operationId":"KeystoreController_sendPasswordHint","summary":"Send password hint to account email","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RequestPasswordHintDto"}}}},"responses":{"201":{"description":""}},"tags":["keystore"]}}},"components":{"schemas":{"RequestPasswordHintDto":{"type":"object","properties":{"email":{"type":"string","description":"Email address of the account to send the password hint to"}},"required":["email"]}}}}
```


# Inscriptions

## POST /api/inscriptions/verify

> Verify and save inscriptions owned by wallet

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/inscriptions/verify":{"post":{"operationId":"InscriptionController_verify","summary":"Verify and save inscriptions owned by wallet","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/VerifyInscriptionsDto"}}}},"responses":{"200":{"description":""}},"tags":["inscriptions"]}}},"components":{"schemas":{"VerifyInscriptionsDto":{"type":"object","properties":{"inscriptionIds":{"description":"Array of inscription IDs to verify","minItems":1,"maxItems":3,"type":"array","items":{"type":"string"}},"walletAddress":{"type":"string"}},"required":["inscriptionIds"]}}}}
```


# User Session

## GET /api/user-session

> List sessions — self for users (with isCurrent), all for admin/super\_admin (no isCurrent)

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}}},"paths":{"/api/user-session":{"get":{"operationId":"UserSessionController_paginate","summary":"List sessions — self for users (with isCurrent), all for admin/super_admin (no isCurrent)","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (default: 1)","schema":{"type":"number"}},{"name":"pageSize","required":false,"in":"query","description":"Number of items per page (default: 10)","schema":{"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Sort field and order. Use - prefix for descending. Example: -createdAt, createdAt","schema":{"type":"string"}},{"name":"select","required":false,"in":"query","description":"Fields to select (comma separated). Use + prefix to include hidden fields. Example: name,status,+holders","schema":{"type":"string"}},{"name":"populate","required":false,"in":"query","description":"Relations to populate (comma separated). Example: wallet,token","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["user-session"]}}}}
```

## DELETE /api/user-session

> Revoke all sessions except the current one

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/user-session":{"delete":{"operationId":"UserSessionController_revokeAllOtherSessions","summary":"Revoke all sessions except the current one","parameters":[],"responses":{"200":{"description":""}},"tags":["user-session"]}}}}
```

## DELETE /api/user-session/{id}

> Revoke a specific session by ID

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/user-session/{id}":{"delete":{"operationId":"UserSessionController_revokeSession","summary":"Revoke a specific session by ID","parameters":[{"name":"id","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["user-session"]}}}}
```


# Auth

## Authenticate user with BIP322 signature verification

> Authenticates a user by verifying their BIP322 signature and creates a wallet with JWT tokens

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/auth/authenticate":{"post":{"operationId":"AuthController_authenticate","summary":"Authenticate user with BIP322 signature verification","description":"Authenticates a user by verifying their BIP322 signature and creates a wallet with JWT tokens","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/AuthenticateDto"}}}},"responses":{"200":{"description":"User authenticated successfully","content":{"application/json":{"schema":{"$ref":"#/components/schemas/BaseResponseDto"}}}},"400":{"description":"Invalid signature, invalid address, or authentication failed","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"string"}}}}}}},"tags":["auth"]}}},"components":{"schemas":{"AuthenticateDto":{"type":"object","properties":{"message":{"type":"string","description":"The message that was signed"},"signature":{"type":"string","description":"The BIP322 signature of the message"},"address":{"type":"string","description":"The Bitcoin address that signed the message"},"publicKey":{"type":"string","description":"The public key used for signing"}},"required":["message","signature","address","publicKey"]},"BaseResponseDto":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"data":{"type":"object"},"metaData":{"$ref":"#/components/schemas/ResponseMetaData"}},"required":["code","message","data","metaData"]},"ResponseMetaData":{"type":"object","properties":{"totalItems":{"type":"number"},"currentPage":{"type":"number"},"pageSize":{"type":"number"},"totalPages":{"type":"number"}},"required":["totalItems","currentPage","pageSize","totalPages"]}}}}
```

## POST /api/auth/refresh-token

> Refresh access token using refresh token

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/auth/refresh-token":{"post":{"operationId":"AuthController_refreshToken","summary":"Refresh access token using refresh token","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RefreshTokenDto"}}}},"responses":{"200":{"description":"New access token and rotated refresh token generated successfully"},"400":{"description":"Invalid or expired refresh token","content":{"application/json":{"schema":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"details":{"type":"string"}}}}}}},"tags":["auth"]}}},"components":{"schemas":{"RefreshTokenDto":{"type":"object","properties":{"refreshToken":{"type":"string","description":"Refresh token to generate new access token"}},"required":["refreshToken"]}}}}
```

## GET /api/auth/webauthn/challenge

> Get WebAuthn challenge for passkey registration or login

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/auth/webauthn/challenge":{"get":{"operationId":"AuthController_getWebAuthnChallenge","summary":"Get WebAuthn challenge for passkey registration or login","parameters":[],"responses":{"200":{"description":""}},"tags":["auth"]}}}}
```

## POST /api/auth/email-verification/request

> Request email OTP for registration — sends a 6-digit code to the given email address

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/auth/email-verification/request":{"post":{"operationId":"AuthController_requestEmailVerification","summary":"Request email OTP for registration — sends a 6-digit code to the given email address","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/EmailVerificationRequestDto"}}}},"responses":{"200":{"description":"OTP queued"},"400":{"description":"Email already registered"},"429":{"description":"Rate limit exceeded"}},"tags":["auth"]}}},"components":{"schemas":{"EmailVerificationRequestDto":{"type":"object","properties":{"email":{"type":"string","description":"Email address to send OTP to"}},"required":["email"]}}}}
```

## POST /api/auth/register

> Register a new bound wallet account

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/auth/register":{"post":{"operationId":"AuthController_register","summary":"Register a new bound wallet account","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RegisterDto"}}}},"responses":{"201":{"description":"Account registered successfully"}},"tags":["auth"]}}},"components":{"schemas":{"RegisterDto":{"type":"object","properties":{"email":{"type":"string"},"otp":{"type":"string"},"authType":{"type":"string","enum":["passkey","srp","wallet"]},"passkey":{"$ref":"#/components/schemas/RegisterPasskeyDto"},"srpData":{"$ref":"#/components/schemas/RegisterSrpDto"},"walletAuth":{"$ref":"#/components/schemas/RegisterWalletAuthDto"},"wallets":{"$ref":"#/components/schemas/RegisterWalletsDto"},"referralCode":{"type":"string","description":"Referral code (base64url-encoded referrer accountId). Throws if invalid."}},"required":["email","otp","authType","wallets"]},"RegisterPasskeyDto":{"type":"object","properties":{"challengeId":{"type":"string"},"credentialId":{"type":"string"},"webauthnPublicKey":{"type":"string"},"attestation":{"type":"string"},"clientDataJson":{"type":"string"},"authenticatorData":{"type":"string"},"encryptedBlob":{"type":"string"},"deviceName":{"type":"string"}},"required":["challengeId","credentialId","webauthnPublicKey","attestation","clientDataJson","authenticatorData","encryptedBlob"]},"RegisterSrpDto":{"type":"object","properties":{"srpSalt":{"type":"string","description":"Random salt generated client-side (base64, 32 bytes)"},"srpVerifier":{"type":"string","description":"SRP verifier v = g^x mod N (base64), x = H(salt || H(email:password))"},"encryptedBlob":{"type":"string"},"passwordHint":{"type":"string"}},"required":["srpSalt","srpVerifier","encryptedBlob"]},"RegisterWalletAuthDto":{"type":"object","properties":{"address":{"type":"string"},"chain":{"type":"string","enum":["evm","solana","bitcoin"]},"publicKey":{"type":"string"},"provider":{"type":"string"},"challengeId":{"type":"string"},"signature":{"type":"string"},"encryptedBlob":{"type":"string"}},"required":["address","chain","publicKey","provider","challengeId","signature","encryptedBlob"]},"RegisterWalletsDto":{"type":"object","properties":{"btc":{"$ref":"#/components/schemas/RegisterBtcWalletDto"},"evm":{"$ref":"#/components/schemas/RegisterEvmWalletDto"},"sol":{"$ref":"#/components/schemas/RegisterSolWalletDto"}},"required":["btc","evm","sol"]},"RegisterBtcWalletDto":{"type":"object","properties":{"publicKey":{"type":"string"},"address":{"type":"string"},"message":{"type":"string"},"signature":{"type":"string"}},"required":["publicKey","address","message","signature"]},"RegisterEvmWalletDto":{"type":"object","properties":{"publicKey":{"type":"string"},"message":{"type":"string"},"signature":{"type":"string"}},"required":["publicKey","message","signature"]},"RegisterSolWalletDto":{"type":"object","properties":{"publicKey":{"type":"string"},"message":{"type":"string"},"signature":{"type":"string"}},"required":["publicKey","message","signature"]}}}}
```

## POST /api/auth/passkey/login

> Login with passkey or password

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/auth/passkey/login":{"post":{"operationId":"AuthController_login","summary":"Login with passkey or password","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/LoginDto"}}}},"responses":{"200":{"description":"Login successful"}},"tags":["auth"]}}},"components":{"schemas":{"LoginDto":{"type":"object","properties":{"credentialId":{"type":"string"},"challengeId":{"type":"string"},"webauthnAssertion":{"$ref":"#/components/schemas/WebAuthnAssertionDto"}},"required":["credentialId","challengeId","webauthnAssertion"]},"WebAuthnAssertionDto":{"type":"object","properties":{"authenticatorData":{"type":"string"},"clientDataJson":{"type":"string"},"signature":{"type":"string"}},"required":["authenticatorData","clientDataJson","signature"]}}}}
```

## SRP step 1 — get salt and server public key

> Client sends email. Server returns salt and serverPublic (B) for SRP-6a authentication.

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/auth/srp/init":{"post":{"operationId":"AuthController_srpInit","summary":"SRP step 1 — get salt and server public key","description":"Client sends email. Server returns salt and serverPublic (B) for SRP-6a authentication.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SrpInitDto"}}}},"responses":{"200":{"description":"Returns sessionId, salt, and serverPublic (B)"}},"tags":["auth"]}}},"components":{"schemas":{"SrpInitDto":{"type":"object","properties":{"email":{"type":"string","description":"Account email"}},"required":["email"]}}}}
```

## POST /api/auth/logout

> Logout — revoke current session and refresh token

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}}},"paths":{"/api/auth/logout":{"post":{"operationId":"AuthController_logout","summary":"Logout — revoke current session and refresh token","parameters":[],"responses":{"200":{"description":"Logged out successfully"}},"tags":["auth"]}}}}
```

## SRP step 2 — verify client proof and complete login

> Client sends clientPublic (A) and clientProof (M1). Server verifies and returns serverProof (M2) plus tokens.

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/auth/srp/verify":{"post":{"operationId":"AuthController_srpVerify","summary":"SRP step 2 — verify client proof and complete login","description":"Client sends clientPublic (A) and clientProof (M1). Server verifies and returns serverProof (M2) plus tokens.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SrpVerifyDto"}}}},"responses":{"200":{"description":"Login successful — returns serverProof, tokens, and encryptedBlob"}},"tags":["auth"]}}},"components":{"schemas":{"SrpVerifyDto":{"type":"object","properties":{"sessionId":{"type":"string","description":"Session ID returned by srp/init"},"clientPublic":{"type":"string","description":"Client ephemeral public key A = g^a mod N (base64)"},"clientProof":{"type":"string","description":"Client proof M1 = H(H(N)⊕H(g) || H(I) || salt || A || B || K) (base64)"}},"required":["sessionId","clientPublic","clientProof"]}}}}
```

## GET /api/auth/wallet/challenge

> Wallet auth — issue a fresh login nonce for an auth wallet address

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/auth/wallet/challenge":{"get":{"operationId":"AuthController_getWalletChallenge","summary":"Wallet auth — issue a fresh login nonce for an auth wallet address","parameters":[{"name":"address","required":true,"in":"query","description":"Auth wallet address to issue a login nonce for","schema":{"type":"string"}}],"responses":{"200":{"description":"Returns challengeId, challenge (nonce), expiresAt"}},"tags":["auth"]}}}}
```

## POST /api/auth/wallet/login

> Wallet auth — log in by signing the issued nonce

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/auth/wallet/login":{"post":{"operationId":"AuthController_walletLogin","summary":"Wallet auth — log in by signing the issued nonce","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/WalletLoginDto"}}}},"responses":{"200":{"description":"Login successful — tokens, encryptedBlob, account, wallets"}},"tags":["auth"]}}},"components":{"schemas":{"WalletLoginDto":{"type":"object","properties":{"address":{"type":"string","description":"Registered auth wallet address"},"challengeId":{"type":"string","description":"challengeId from GET /auth/wallet/challenge"},"signature":{"type":"string","description":"Wallet signature over the issued server nonce"}},"required":["address","challengeId","signature"]}}}}
```

## 2FA TOTP verify — exchange pending token + OTP code for full JWT pair

> Accepts the pendingToken returned by srp/verify when 2FA is enabled, plus the current TOTP code. Returns the full login response.

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/auth/2fa/verify":{"post":{"operationId":"AuthController_verifyTotpLogin","summary":"2FA TOTP verify — exchange pending token + OTP code for full JWT pair","description":"Accepts the pendingToken returned by srp/verify when 2FA is enabled, plus the current TOTP code. Returns the full login response.","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/TotpVerifyDto"}}}},"responses":{"200":{"description":"OTP verified — returns accessToken, refreshToken, encryptedBlob, account, wallets"},"400":{"description":"Invalid or expired pendingToken, or wrong OTP code"}},"tags":["auth"]}}},"components":{"schemas":{"TotpVerifyDto":{"type":"object","properties":{}}}}}
```


# Audit Log

## GET /api/audit-log

> Admin: list audit logs across all accounts

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}}},"paths":{"/api/audit-log":{"get":{"operationId":"AuditLogController_paginate","summary":"Admin: list audit logs across all accounts","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (default: 1)","schema":{"type":"number"}},{"name":"pageSize","required":false,"in":"query","description":"Number of items per page (default: 10)","schema":{"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Sort field and order. Use - prefix for descending. Example: -createdAt, createdAt","schema":{"type":"string"}},{"name":"select","required":false,"in":"query","description":"Fields to select (comma separated). Use + prefix to include hidden fields. Example: name,status,+holders","schema":{"type":"string"}},{"name":"populate","required":false,"in":"query","description":"Relations to populate (comma separated). Example: wallet,token","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["audit-log"]}}}}
```


# Achievements

## GET /api/achievements/definitions

> List all achievement definitions (live + metadata)

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/achievements/definitions":{"get":{"operationId":"AchievementController_list","summary":"List all achievement definitions (live + metadata)","parameters":[],"responses":{"200":{"description":""}},"tags":["achievements"]}}}}
```

## GET /api/achievements/me

> Caller's achievement completion state + drop-eligibility

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}}},"paths":{"/api/achievements/me":{"get":{"operationId":"AchievementController_me","summary":"Caller's achievement completion state + drop-eligibility","parameters":[],"responses":{"200":{"description":""}},"tags":["achievements"]}}}}
```


# Referral Claim

## GET /api/referral-claim

> Caller's referral claims.

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}}},"paths":{"/api/referral-claim":{"get":{"operationId":"ReferralClaimController_paginate","summary":"Caller's referral claims.","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (default: 1)","schema":{"type":"number"}},{"name":"pageSize","required":false,"in":"query","description":"Number of items per page (default: 10)","schema":{"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Sort field and order. Use - prefix for descending. Example: -createdAt, createdAt","schema":{"type":"string"}},{"name":"select","required":false,"in":"query","description":"Fields to select (comma separated). Use + prefix to include hidden fields. Example: name,status,+holders","schema":{"type":"string"}},{"name":"populate","required":false,"in":"query","description":"Relations to populate (comma separated). Example: wallet,token","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["referral-claim"]}}}}
```


# VM Block

## GET /api/vm-blocks

> Get VM blocks

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/vm-blocks":{"get":{"operationId":"VmBlockController_getVmBlock","summary":"Get VM blocks","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (default: 1)","schema":{"type":"number"}},{"name":"pageSize","required":false,"in":"query","description":"Number of items per page (default: 10)","schema":{"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Sort field and order. Use - prefix for descending. Example: -createdAt, createdAt","schema":{"type":"string"}},{"name":"select","required":false,"in":"query","description":"Fields to select (comma separated). Use + prefix to include hidden fields. Example: name,status,+holders","schema":{"type":"string"}},{"name":"populate","required":false,"in":"query","description":"Relations to populate (comma separated). Example: wallet,token","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["VM Block"]}}}}
```

## GET /api/vm-blocks/incoming

>

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/vm-blocks/incoming":{"get":{"operationId":"VmBlockController_getIncoming","parameters":[],"responses":{"200":{"description":""}},"tags":["VM Block"]}}}}
```

## GET /api/vm-blocks/difficulty-adjustment

>

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/vm-blocks/difficulty-adjustment":{"get":{"operationId":"VmBlockController_getDifficultyAdjustment","parameters":[],"responses":{"200":{"description":""}},"tags":["VM Block"]}}}}
```


# Pending Message

## GET /api/pending-message

> Admin: list pending messages with pagination

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}}},"paths":{"/api/pending-message":{"get":{"operationId":"PendingMessageController_paginate","summary":"Admin: list pending messages with pagination","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (default: 1)","schema":{"type":"number"}},{"name":"pageSize","required":false,"in":"query","description":"Number of items per page (default: 10)","schema":{"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Sort field and order. Use - prefix for descending. Example: -createdAt, createdAt","schema":{"type":"string"}},{"name":"select","required":false,"in":"query","description":"Fields to select (comma separated). Use + prefix to include hidden fields. Example: name,status,+holders","schema":{"type":"string"}},{"name":"populate","required":false,"in":"query","description":"Relations to populate (comma separated). Example: wallet,token","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["pending-message"]}}}}
```


# Medias

## POST /api/medias/presigned-url

> Get presigned url

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/medias/presigned-url":{"post":{"operationId":"MediaController_presignedUrl","summary":"Get presigned url","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/PresignedUrlDto"}}}},"responses":{"200":{"description":"Presigned url","content":{"application/json":{"schema":{"allOf":[{"$ref":"#/components/schemas/BaseResponseDto"},{"properties":{"data":{"type":"array","items":{"$ref":"#/components/schemas/RuneEtchMediaEntity"}}}}]}}}}},"tags":["medias"]}}},"components":{"schemas":{"PresignedUrlDto":{"type":"object","properties":{"files":{"type":"array","items":{"$ref":"#/components/schemas/ItemPresignedUrlDto"}},"userAddress":{"type":"string"}},"required":["files","userAddress"]},"ItemPresignedUrlDto":{"type":"object","properties":{"filename":{"type":"string"}},"required":["filename"]},"BaseResponseDto":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"data":{"type":"object"},"metaData":{"$ref":"#/components/schemas/ResponseMetaData"}},"required":["code","message","data","metaData"]},"ResponseMetaData":{"type":"object","properties":{"totalItems":{"type":"number"},"currentPage":{"type":"number"},"pageSize":{"type":"number"},"totalPages":{"type":"number"}},"required":["totalItems","currentPage","pageSize","totalPages"]},"RuneEtchMediaEntity":{"type":"object","properties":{"key":{"type":"string"},"url":{"type":"string"},"signedUrl":{"$ref":"#/components/schemas/ISignedUrl"},"fileName":{"type":"string"},"mimeType":{"type":"string"},"size":{"type":"number"}},"required":["key","url","signedUrl","fileName","mimeType","size"]},"ISignedUrl":{"type":"object","properties":{"url":{"type":"string"},"fields":{"$ref":"#/components/schemas/ISignedFields"}},"required":["url","fields"]},"ISignedFields":{"type":"object","properties":{"key":{"type":"string"},"Policy":{"type":"string"},"bucket":{"type":"string"},"X-Amz-Date":{"type":"string"},"Content-Type":{"type":"string"},"X-Amz-Algorithm":{"type":"string"},"X-Amz-Signature":{"type":"string"},"X-Amz-Credential":{"type":"string"}},"required":["key","Policy","bucket","X-Amz-Date","Content-Type","X-Amz-Algorithm","X-Amz-Signature","X-Amz-Credential"]}}}}
```


# Policy Signature

## POST /api/policy-signature

>

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/policy-signature":{"post":{"operationId":"PolicySignatureController_create","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreatePolicySignatureDto"}}}},"responses":{"201":{"description":""}},"tags":["policy-signature"]}}},"components":{"schemas":{"CreatePolicySignatureDto":{"type":"object","properties":{"message":{"type":"string"},"userAddress":{"type":"string"},"signature":{"type":"string"}},"required":["message","userAddress","signature"]}}}}
```


# Setting

## GET /api/setting

>

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/setting":{"get":{"operationId":"SettingController_getSetting","parameters":[],"responses":{"200":{"description":""}},"tags":["Setting"]}}}}
```


# Refund

## POST /api/refund

> Create refund transaction

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"CreateRefundDto":{"type":"object","properties":{"type":{"type":"string","enum":["virtual_mint"]},"runeIds":{"description":"require if refund type is \"virtual_mint\"","type":"array","items":{"type":"string"}},"userAddress":{"type":"string"}},"required":["type","userAddress"]}}},"paths":{"/api/refund":{"post":{"operationId":"RefundController_create","summary":"Create refund transaction","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateRefundDto"}}}},"responses":{"201":{"description":""}},"tags":["Refund"]}}}}
```

## POST /api/refund/sign

> Sign refund transaction

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"BroadcastRefundDto":{"type":"object","properties":{"base64Psbt":{"type":"string"},"userAddress":{"type":"string"}},"required":["base64Psbt","userAddress"]}}},"paths":{"/api/refund/sign":{"post":{"operationId":"RefundController_signAndBroadcast","summary":"Sign refund transaction","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/BroadcastRefundDto"}}}},"responses":{"201":{"description":""}},"tags":["Refund"]}}}}
```

## GET /api/refund/calculate

> calculate refund amount

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/refund/calculate":{"get":{"operationId":"RefundController_calculate","summary":"calculate refund amount","parameters":[{"name":"type","required":true,"in":"query","schema":{"enum":["virtual_mint"],"type":"string"}},{"name":"runeIds","required":false,"in":"query","description":"require if refund type is \"virtual_mint\"","schema":{"type":"string"}},{"name":"userAddress","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Refund"]}}}}
```


# Event Source

## GET /api/event-source/sse

>

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/event-source/sse":{"get":{"operationId":"EventSourceController_sse","parameters":[{"name":"userAddress","required":true,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["Event Source"]}}}}
```


# Activity Feed

## Get RadFi AMM pool activity feed

> Returns a paginated list of successful RadFi AMM pool swaps (histories), sorted by most recent first.

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/activity-feed/radfi":{"get":{"operationId":"ActivityFeedController_getRadfiActivityFeed","summary":"Get RadFi AMM pool activity feed","description":"Returns a paginated list of successful RadFi AMM pool swaps (histories), sorted by most recent first.","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (default: 1)","schema":{"type":"number"}},{"name":"pageSize","required":false,"in":"query","description":"Number of items per page (default: 10)","schema":{"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Sort field and order. Use - prefix for descending. Example: -createdAt, createdAt","schema":{"type":"string"}},{"name":"select","required":false,"in":"query","description":"Fields to select (comma separated). Use + prefix to include hidden fields. Example: name,status,+holders","schema":{"type":"string"}},{"name":"populate","required":false,"in":"query","description":"Relations to populate (comma separated). Example: wallet,token","schema":{"type":"string"}}],"responses":{"200":{"description":"RadFi pool activity feed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivityFeedResponseDto"}}}}},"tags":["activity-feed"]}}},"components":{"schemas":{"ActivityFeedResponseDto":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ActivityFeedItemDto"}},"metaData":{"$ref":"#/components/schemas/ResponseMetaData"}},"required":["code","message","data","metaData"]},"ActivityFeedItemDto":{"type":"object","properties":{"source":{"type":"string","enum":["amm","sodax"]},"pair":{"type":"string"},"amountSold":{"type":"string"},"tokenSoldSymbol":{"type":"string","description":"Symbol of token sold (for display)"},"amountReceived":{"type":"string"},"tokenReceivedSymbol":{"type":"string","description":"Symbol of token received (for display)"},"tradeValueUsd":{"type":"number"},"trader":{"type":"string"},"time":{"type":"number"},"txId":{"type":"string"}},"required":["source","pair","amountSold","tokenSoldSymbol","amountReceived","tokenReceivedSymbol","tradeValueUsd","trader","time","txId"]},"ResponseMetaData":{"type":"object","properties":{"totalItems":{"type":"number"},"currentPage":{"type":"number"},"pageSize":{"type":"number"},"totalPages":{"type":"number"}},"required":["totalItems","currentPage","pageSize","totalPages"]}}}}
```

## Get SODAX solver swap activity feed

> Returns a paginated list of SODAX solver-swap transactions (partner\_transactions), sorted by most recent first.

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/activity-feed/sodax":{"get":{"operationId":"ActivityFeedController_getSodaxActivityFeed","summary":"Get SODAX solver swap activity feed","description":"Returns a paginated list of SODAX solver-swap transactions (partner_transactions), sorted by most recent first.","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (default: 1)","schema":{"type":"number"}},{"name":"pageSize","required":false,"in":"query","description":"Number of items per page (default: 10)","schema":{"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Sort field and order. Use - prefix for descending. Example: -createdAt, createdAt","schema":{"type":"string"}},{"name":"select","required":false,"in":"query","description":"Fields to select (comma separated). Use + prefix to include hidden fields. Example: name,status,+holders","schema":{"type":"string"}},{"name":"populate","required":false,"in":"query","description":"Relations to populate (comma separated). Example: wallet,token","schema":{"type":"string"}}],"responses":{"200":{"description":"SODAX activity feed","content":{"application/json":{"schema":{"$ref":"#/components/schemas/ActivityFeedResponseDto"}}}}},"tags":["activity-feed"]}}},"components":{"schemas":{"ActivityFeedResponseDto":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ActivityFeedItemDto"}},"metaData":{"$ref":"#/components/schemas/ResponseMetaData"}},"required":["code","message","data","metaData"]},"ActivityFeedItemDto":{"type":"object","properties":{"source":{"type":"string","enum":["amm","sodax"]},"pair":{"type":"string"},"amountSold":{"type":"string"},"tokenSoldSymbol":{"type":"string","description":"Symbol of token sold (for display)"},"amountReceived":{"type":"string"},"tokenReceivedSymbol":{"type":"string","description":"Symbol of token received (for display)"},"tradeValueUsd":{"type":"number"},"trader":{"type":"string"},"time":{"type":"number"},"txId":{"type":"string"}},"required":["source","pair","amountSold","tokenSoldSymbol","amountReceived","tokenReceivedSymbol","tradeValueUsd","trader","time","txId"]},"ResponseMetaData":{"type":"object","properties":{"totalItems":{"type":"number"},"currentPage":{"type":"number"},"pageSize":{"type":"number"},"totalPages":{"type":"number"}},"required":["totalItems","currentPage","pageSize","totalPages"]}}}}
```


# Stats

## GET /api/stats

> Get radFi ecosystem stats (TVL, volume, fees, wallet counts, etc.)

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/stats":{"get":{"operationId":"StatsController_getStats","summary":"Get radFi ecosystem stats (TVL, volume, fees, wallet counts, etc.)","parameters":[],"responses":{"200":{"description":""}},"tags":["stats"]}}}}
```


# Sodax

## POST /api/sodax/transaction

> Create Sodax transaction

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"CreateSodaxTxDto":{"type":"object","properties":{"type":{"type":"string","enum":["sodax-withdraw","sodax-withdraw-musig2","sodax-solver-swap"]},"params":{"type":"object"}},"required":["type","params"]}}},"paths":{"/api/sodax/transaction":{"post":{"operationId":"SodaxController_createTransaction","summary":"Create Sodax transaction","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSodaxTxDto"}}}},"responses":{"201":{"description":""}},"tags":["sodax"]}}}}
```

## GET /api/sodax/affiliate

> Get affiliate fee

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}}},"paths":{"/api/sodax/affiliate":{"get":{"operationId":"SodaxController_getAffiliateInfo","summary":"Get affiliate fee","parameters":[],"responses":{"200":{"description":""}},"tags":["sodax"]}}}}
```

## POST /api/sodax/transaction/sign

> Sign and broadcast Sodax transaction

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"SignSodaxTransactionDto":{"type":"object","properties":{"type":{"type":"string","enum":["sodax-withdraw","sodax-withdraw-musig2","sodax-solver-swap"]},"params":{"$ref":"#/components/schemas/SignSodaxTransactionParamsDto"}},"required":["type","params"]},"SignSodaxTransactionParamsDto":{"type":"object","properties":{"userAddress":{"type":"string","description":"Set from JWT token. Do not send from client."},"signedBase64Tx":{"type":"string"},"relayData":{"description":"Relay extra data from SDK createIntent(). Required for BTC relay resubmit.","allOf":[{"$ref":"#/components/schemas/RelayDataDto"}]}},"required":["signedBase64Tx"]},"RelayDataDto":{"type":"object","properties":{"address":{"type":"string","description":"Hub destination address"},"payload":{"type":"string","description":"Encoded intent instruction payload"}},"required":["address","payload"]}}},"paths":{"/api/sodax/transaction/sign":{"post":{"operationId":"SodaxController_signAndBroadcast","summary":"Sign and broadcast Sodax transaction","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignSodaxTransactionDto"}}}},"responses":{"201":{"description":""}},"tags":["sodax"]}}}}
```

## GET /api/sodax/transactions/{txId}/status

> Get swap lifecycle status by txId

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}}},"paths":{"/api/sodax/transactions/{txId}/status":{"get":{"operationId":"SodaxController_getSwapStatus","summary":"Get swap lifecycle status by txId","parameters":[{"name":"txId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":"Swap lifecycle status"}},"tags":["sodax"]}}}}
```

## GET /api/sodax/transactions

> Get Sodax transactions

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/sodax/transactions":{"get":{"operationId":"SodaxController_getTransactions","summary":"Get Sodax transactions","parameters":[{"name":"relatedAddress_eq","required":false,"in":"query","description":"Match rows where tradingAddress OR withdrawTo equals this Bitcoin address (e.g. musig2 withdrawTo).","schema":{"type":"string"}},{"name":"intentToken_eq","required":false,"in":"query","description":"Match rows where intent.inputToken OR intent.outputToken equals this value (e.g. EVM token address). Use with relatedAddress_eq for (intent OR intent) AND (tradingAddress OR withdrawTo).","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","description":"Page number (default: 1)","schema":{"type":"number"}},{"name":"pageSize","required":false,"in":"query","description":"Number of items per page (default: 10)","schema":{"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Sort field and order. Use - prefix for descending. Example: -createdAt, createdAt","schema":{"type":"string"}},{"name":"select","required":false,"in":"query","description":"Fields to select (comma separated). Use + prefix to include hidden fields. Example: name,status,+holders","schema":{"type":"string"}},{"name":"populate","required":false,"in":"query","description":"Relations to populate (comma separated). Example: wallet,token","schema":{"type":"string"}}],"responses":{"200":{"description":"Get Sodax transactions","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SodaxResponseDto"}}}}},"tags":["sodax"]}}},"components":{"schemas":{"SodaxResponseDto":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"data":{"type":"array","items":{"$ref":"#/components/schemas/PartnerTxEntity"}},"metaData":{"$ref":"#/components/schemas/ResponseMetaData"}},"required":["code","message","data","metaData"]},"PartnerTxEntity":{"type":"object","properties":{}},"ResponseMetaData":{"type":"object","properties":{"totalItems":{"type":"number"},"currentPage":{"type":"number"},"pageSize":{"type":"number"},"totalPages":{"type":"number"}},"required":["totalItems","currentPage","pageSize","totalPages"]}}}}
```


# Bound Lending

## POST /api/bound-lending/transaction/sign

> Sign bound lending/option PSBT (origination, repayment, option\_origination, option\_exercise) and broadcast

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/bound-lending/transaction/sign":{"post":{"operationId":"BoundLendingController_signTransaction","summary":"Sign bound lending/option PSBT (origination, repayment, option_origination, option_exercise) and broadcast","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignBoundLendingPsbtDto"}}}},"responses":{"200":{"description":"","content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignBoundLendingPsbtResponseDto"}}}}},"tags":["bound-lending"]}}},"components":{"schemas":{"SignBoundLendingPsbtDto":{"type":"object","properties":{"psbtBase64":{"type":"string","description":"Base64-encoded PSBT with all required signatures except RadFi BE trading wallet co-sign"},"type":{"type":"string","description":"PSBT type: origination, repayment, option_origination, or option_exercise","enum":["origination","repayment","option_origination","option_exercise","order_book_premium"]},"userAccountId":{"type":"string","description":"Borrower's Bound accountId (24-hex Mongo ObjectId). When set on an origination PSBT, the server resolves the borrower's referrer (if any) and enforces that the referrer's address appears as an output receiving a proportional bUSD share of the protocol fee (referralFeeBps / MAX_FEE). Optional — omit to skip referrer validation."},"userTradingAddress":{"type":"string","description":"Trading wallet address of the user who initiates from the FE (borrower for lending, seller for options)"},"solverTradingAddress":{"type":"string","description":"Trading wallet address of the solver/counterparty (lender for lending, buyer for options)"},"borrowerPubkey":{"type":"string","description":"Borrower 33-byte compressed pubkey hex (required for origination/repayment)"},"lenderPubkey":{"type":"string","description":"Lender 33-byte compressed pubkey hex (required for origination/repayment)"},"forfeitureExpiryTimestamp":{"type":"number","description":"Absolute Unix timestamp for forfeiture CLTV leaf (required for origination/repayment)"},"previousLenderPubkey":{"type":"string","description":"Lender 33-byte compressed pubkey hex of the previous loan escrow being rolled over. Required only when the origination PSBT spends a previous escrow input (rollover)."},"previousForfeitureExpiryTimestamp":{"type":"number","description":"Forfeiture CLTV expiry timestamp of the previous loan escrow being rolled over. When set, the origination PSBT is treated as a rollover: input 0 must spend the reconstructed previous escrow (script-path) and the metadata OP_RETURN flag must be 0x04."},"previousLenderTradingAddress":{"type":"string","description":"Previous lender trading wallet address (used in cross-lender rollover to allow the payment output back to the previous lender)."},"sellerPubkey":{"type":"string","description":"Seller 33-byte compressed pubkey hex (required for option_origination/option_exercise)"},"escrowExpiryTimestamp":{"type":"number","description":"Absolute Unix timestamp for escrow CLTV expiry (required for option_origination/option_exercise)"},"hashY":{"type":"string","description":"SHA256 hash Y (32-byte hex) for HTLC escrow (required for option_origination/option_exercise)"}},"required":["psbtBase64","type"]},"SignBoundLendingPsbtResponseDto":{"type":"object","properties":{"txId":{"type":"string","description":"Transaction ID of the broadcasted transaction"},"hex":{"type":"string","description":"Raw transaction hex"}},"required":["txId","hex"]}}}}
```


# Download Histories

## GET /api/download-histories

> List download jobs for the authenticated user

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/download-histories":{"get":{"operationId":"DownloadHistoryController_listDownloads","summary":"List download jobs for the authenticated user","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (default: 1)","schema":{"type":"number"}},{"name":"pageSize","required":false,"in":"query","description":"Number of items per page (default: 10)","schema":{"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Sort field and order. Use - prefix for descending. Example: -createdAt, createdAt","schema":{"type":"string"}},{"name":"select","required":false,"in":"query","description":"Fields to select (comma separated). Use + prefix to include hidden fields. Example: name,status,+holders","schema":{"type":"string"}},{"name":"populate","required":false,"in":"query","description":"Relations to populate (comma separated). Example: wallet,token","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["download-histories"]}}}}
```

## POST /api/download-histories

> Request a new download job

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/download-histories":{"post":{"operationId":"DownloadHistoryController_requestDownload","summary":"Request a new download job","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateDownloadHistoryDto"}}}},"responses":{"201":{"description":""}},"tags":["download-histories"]}}},"components":{"schemas":{"CreateDownloadHistoryDto":{"type":"object","properties":{"format":{"type":"string","enum":["csv","json","pdf"]},"filter":{"$ref":"#/components/schemas/DownloadHistoryFilterDto"}},"required":["format","filter"]},"DownloadHistoryFilterDto":{"type":"object","properties":{"chain":{"type":"string","enum":["bitcoin","ethereum"]},"type":{"type":"string","enum":["swap","withdraw_liquidity","collect_fee","init_pool","provide_liquidity","increase_liquidity","withdraw","withdraw_rune_stable_coin","renew_utxo","protocol_collect_fee","splitting_orders","deposit","satflow_list","satflow_cancel","satflow_buy"]},"from":{"type":"string"},"to":{"type":"string"}}}}}}
```


# Satflow

## POST /api/satflow/build

> Build transaction — LIST, CANCEL, BUY

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"CreateSatflowTxDto":{"type":"object","properties":{"type":{"type":"string","enum":["satflow-list","satflow-cancel","satflow-buy"]},"userAddress":{"type":"string"},"inscriptionId":{"type":"string"},"priceInSats":{"type":"string"},"orderIds":{"type":"array","items":{"type":"string"}},"orderType":{"type":"string"},"inscriptionIds":{"description":"Inscription IDs to buy","type":"array","items":{"type":"string"}}},"required":["type","userAddress"]}}},"paths":{"/api/satflow/build":{"post":{"operationId":"SatflowController_buildTransaction","summary":"Build transaction — LIST, CANCEL, BUY","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/CreateSatflowTxDto"}}}},"responses":{"201":{"description":""}},"tags":["satflow"]}}}}
```

## POST /api/satflow/sign

> Submit signed transaction to Satflow

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"SignSatflowTxDto":{"type":"object","properties":{"type":{"type":"string","enum":["satflow-list","satflow-cancel","satflow-buy"]},"userAddress":{"type":"string"},"signedPsbt":{"type":"string"},"intentId":{"type":"string"},"signedChallenge":{"type":"string"},"signedSecureListingPsbts":{"type":"array","items":{"type":"string"}},"signedPaymentPrepPsbts":{"type":"array","items":{"type":"string"}},"signedPurchasePsbts":{"type":"array","items":{"type":"string"}},"signedTransferPsbt":{"type":"string"},"feeRate":{"type":"number"}},"required":["type","userAddress","intentId"]}}},"paths":{"/api/satflow/sign":{"post":{"operationId":"SatflowController_signAndSubmit","summary":"Submit signed transaction to Satflow","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SignSatflowTxDto"}}}},"responses":{"201":{"description":""}},"tags":["satflow"]}}}}
```

## POST /api/satflow/buy/revert

> Revert a pending BUY intent — rolls back UMS state for any sub-txids registered so far

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"RevertSatflowBuyDto":{"type":"object","properties":{"inscriptionId":{"type":"string","description":"Inscription ID of the BUY intent to revert (FE tracks this, not the server-generated txId)"},"userAddress":{"type":"string"}},"required":["inscriptionId","userAddress"]}}},"paths":{"/api/satflow/buy/revert":{"post":{"operationId":"SatflowController_revertBuy","summary":"Revert a pending BUY intent — rolls back UMS state for any sub-txids registered so far","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/RevertSatflowBuyDto"}}}},"responses":{"201":{"description":""}},"tags":["satflow"]}}}}
```

## GET /api/satflow/item

> Get inscription item details from Satflow

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/satflow/item":{"get":{"operationId":"SatflowController_getItem","summary":"Get inscription item details from Satflow","parameters":[{"name":"inscriptionId","required":false,"in":"query","schema":{"type":"string"}},{"name":"inscriptionNumber","required":false,"in":"query","schema":{"type":"string"}},{"name":"metadata","required":false,"in":"query","schema":{"type":"boolean"}},{"name":"bid","required":false,"in":"query","schema":{"type":"boolean"}},{"name":"listing","required":false,"in":"query","schema":{"type":"boolean"}},{"name":"exclude_ord","required":false,"in":"query","schema":{"type":"boolean"}}],"responses":{"200":{"description":""}},"tags":["satflow"]}}}}
```

## GET /api/satflow/orders/floor

> Get floor orders for collections from Satflow

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/satflow/orders/floor":{"get":{"operationId":"SatflowController_getOrdersFloor","summary":"Get floor orders for collections from Satflow","parameters":[{"name":"collectionIds","required":false,"in":"query","description":"Single value (?collectionIds=a), CSV (?collectionIds=a,b), or repeat-key (?collectionIds[]=a&collectionIds[]=b)","schema":{"type":"array","items":{"type":"string"}}}],"responses":{"200":{"description":""}},"tags":["satflow"]}}}}
```

## GET /api/satflow/collection-stats

> Get collection statistics from Satflow

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/satflow/collection-stats":{"get":{"operationId":"SatflowController_getCollectionStats","summary":"Get collection statistics from Satflow","parameters":[{"name":"collectionId","required":false,"in":"query","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["satflow"]}}}}
```

## GET /api/satflow/collection-stats/floors

> Get floor prices for multiple ordinals collections from Satflow

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/satflow/collection-stats/floors":{"get":{"operationId":"SatflowController_getCollectionStatsFloors","summary":"Get floor prices for multiple ordinals collections from Satflow","parameters":[{"name":"slugs","required":true,"in":"query","description":"Single value (?slugs=a), CSV (?slugs=a,b), or repeat-key (?slugs[]=a&slugs[]=b)","schema":{"type":"array","items":{"type":"string"}}}],"responses":{"200":{"description":""}},"tags":["satflow"]}}}}
```

## GET /api/satflow/listings

> Get activity listings from Satflow

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/satflow/listings":{"get":{"operationId":"SatflowController_getListings","summary":"Get activity listings from Satflow","parameters":[{"name":"collectionSlug","required":false,"in":"query","schema":{"type":"string"}},{"name":"group","required":false,"in":"query","schema":{"enum":["collection","time"],"type":"string"}},{"name":"active","required":false,"in":"query","schema":{"type":"boolean"}},{"name":"page","required":false,"in":"query","schema":{"type":"number"}},{"name":"pageSize","required":false,"in":"query","schema":{"type":"number"}},{"name":"external","required":false,"in":"query","schema":{"type":"boolean"}},{"name":"timeRange","required":false,"in":"query","schema":{"enum":["24h","7d","30d"],"type":"string"}},{"name":"includeOnlyCollectionItems","required":false,"in":"query","schema":{"type":"boolean"}},{"name":"sortBy","required":false,"in":"query","schema":{"enum":["createdAt","fillCompletedAt","fillPendingAt","price","unitPrice"],"type":"string"}},{"name":"sortDirection","required":false,"in":"query","schema":{"enum":["asc","desc"],"type":"string"}}],"responses":{"200":{"description":""}},"tags":["satflow"]}}}}
```

## GET /api/satflow/wallet-contents

> Get wallet contents from Satflow — ordinals and runes with listing/bid status

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/satflow/wallet-contents":{"get":{"operationId":"SatflowController_getWalletContents","summary":"Get wallet contents from Satflow — ordinals and runes with listing/bid status","parameters":[{"name":"address","required":true,"in":"query","schema":{"type":"string"}},{"name":"itemType","required":false,"in":"query","schema":{"enum":["inscription","rune","all"],"type":"string"}},{"name":"collection","required":false,"in":"query","schema":{"type":"string"}},{"name":"listedOnly","required":false,"in":"query","schema":{"type":"boolean"}},{"name":"bidsOnly","required":false,"in":"query","schema":{"type":"boolean"}},{"name":"cursor","required":false,"in":"query","schema":{"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"type":"number"}}],"responses":{"200":{"description":""}},"tags":["satflow"]}}}}
```

## GET /api/satflow/collections/trending

> Get trending collections from Satflow memflow

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/satflow/collections/trending":{"get":{"operationId":"SatflowController_getTrendingCollections","summary":"Get trending collections from Satflow memflow","parameters":[{"name":"sortBy","required":false,"in":"query","schema":{"type":"string"}},{"name":"sortDirection","required":false,"in":"query","schema":{"enum":["asc","desc"],"type":"string"}},{"name":"offset","required":false,"in":"query","schema":{"type":"number"}},{"name":"limit","required":false,"in":"query","schema":{"type":"number"}}],"responses":{"200":{"description":""}},"tags":["satflow"]}}}}
```

## GET /api/satflow/collection/inscriptions

> Get inscription details for a collection (paginated, max 10 per page)

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/satflow/collection/inscriptions":{"get":{"operationId":"SatflowController_getCollectionInscriptions","summary":"Get inscription details for a collection (paginated, max 10 per page)","parameters":[{"name":"collection_id","required":true,"in":"query","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","description":"Page number, 1-indexed (default 1)","schema":{"type":"number"}},{"name":"pageSize","required":false,"in":"query","description":"Items per page, max 10 — BE fans out 1 /item call per id (default 10)","schema":{"type":"number"}}],"responses":{"200":{"description":""}},"tags":["satflow"]}}}}
```

## GET /api/satflow/collection/listings

> Get active listings for a collection (paginated, sorted by price)

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/satflow/collection/listings":{"get":{"operationId":"SatflowController_getCollectionListings","summary":"Get active listings for a collection (paginated, sorted by price)","parameters":[{"name":"collectionId","required":true,"in":"query","description":"Satflow collection slug (e.g. \"nodemonkes\")","schema":{"type":"string"}},{"name":"page","required":false,"in":"query","description":"Page number, 1-indexed (default 1)","schema":{"type":"number"}},{"name":"pageSize","required":false,"in":"query","description":"Items per page (1–20, default 20) — listings are enriched from a metadata cache; only uncached inscriptions trigger a /item call","schema":{"type":"number"}},{"name":"sortDirection","required":false,"in":"query","description":"Sort by listing price (default asc)","schema":{"enum":["asc","desc"],"type":"string"}}],"responses":{"200":{"description":""}},"tags":["satflow"]}}}}
```

## GET /api/satflow/status/{intentId}

> Get intent status by intent ID (Mongo \_id)

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/satflow/status/{intentId}":{"get":{"operationId":"SatflowController_getStatus","summary":"Get intent status by intent ID (Mongo _id)","parameters":[{"name":"intentId","required":true,"in":"path","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["satflow"]}}}}
```


# Drops

## GET /api/drops/current

> Current live drop summary (pool, countdown, prizes remaining)

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/drops/current":{"get":{"operationId":"OrdinalDropController_current","summary":"Current live drop summary (pool, countdown, prizes remaining)","parameters":[],"responses":{"200":{"description":""}},"tags":["drops"]}}}}
```

## GET /api/drops

> Completed drop history (winners, seed block + hash)

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/drops":{"get":{"operationId":"OrdinalDropController_history","summary":"Completed drop history (winners, seed block + hash)","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (default: 1)","schema":{"type":"number"}},{"name":"pageSize","required":false,"in":"query","description":"Number of items per page (default: 10)","schema":{"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Sort field and order. Use - prefix for descending. Example: -createdAt, createdAt","schema":{"type":"string"}},{"name":"select","required":false,"in":"query","description":"Fields to select (comma separated). Use + prefix to include hidden fields. Example: name,status,+holders","schema":{"type":"string"}},{"name":"populate","required":false,"in":"query","description":"Relations to populate (comma separated). Example: wallet,token","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["drops"]}}}}
```

## GET /api/drops/derivation

> Published winner-derivation formula

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/drops/derivation":{"get":{"operationId":"OrdinalDropController_derivation","summary":"Published winner-derivation formula","parameters":[],"responses":{"200":{"description":""}},"tags":["drops"]}}}}
```

## POST /api/drops/claim

> Daily check-in claim — reveals today’s tickets

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}}},"paths":{"/api/drops/claim":{"post":{"operationId":"OrdinalDropController_claim","summary":"Daily check-in claim — reveals today’s tickets","parameters":[],"responses":{"201":{"description":""}},"tags":["drops"]}}}}
```

## GET /api/drops/claim/status

> Whether the daily claim is available + your tickets

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}}},"paths":{"/api/drops/claim/status":{"get":{"operationId":"OrdinalDropController_claimStatus","summary":"Whether the daily claim is available + your tickets","parameters":[],"responses":{"200":{"description":""}},"tags":["drops"]}}}}
```

## GET /api/drops/{dropNumber}

> Single drop detail

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/drops/{dropNumber}":{"get":{"operationId":"OrdinalDropController_byNumber","summary":"Single drop detail","parameters":[{"name":"dropNumber","required":true,"in":"path","schema":{"type":"number"}}],"responses":{"200":{"description":""}},"tags":["drops"]}}}}
```


# Drop Ticket

## GET /api/drop-ticket

> Public drop-ticket ledger (paginated). Filter by ?dropId=\<drop.\_id> to reconstruct a drop's pool (sum ticketsAwarded per accountId).

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"paths":{"/api/drop-ticket":{"get":{"operationId":"DropTicketController_paginate","summary":"Public drop-ticket ledger (paginated). Filter by ?dropId=<drop._id> to reconstruct a drop's pool (sum ticketsAwarded per accountId).","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (default: 1)","schema":{"type":"number"}},{"name":"pageSize","required":false,"in":"query","description":"Number of items per page (default: 10)","schema":{"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Sort field and order. Use - prefix for descending. Example: -createdAt, createdAt","schema":{"type":"string"}},{"name":"select","required":false,"in":"query","description":"Fields to select (comma separated). Use + prefix to include hidden fields. Example: name,status,+holders","schema":{"type":"string"}},{"name":"populate","required":false,"in":"query","description":"Relations to populate (comma separated). Example: wallet,token","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["drop-ticket"]}}}}
```


# Prizes

## GET /api/prizes

> List prize inventory (admin)

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}}},"paths":{"/api/prizes":{"get":{"operationId":"PrizeController_list","summary":"List prize inventory (admin)","parameters":[{"name":"page","required":false,"in":"query","description":"Page number (default: 1)","schema":{"type":"number"}},{"name":"pageSize","required":false,"in":"query","description":"Number of items per page (default: 10)","schema":{"type":"number"}},{"name":"sort","required":false,"in":"query","description":"Sort field and order. Use - prefix for descending. Example: -createdAt, createdAt","schema":{"type":"string"}},{"name":"select","required":false,"in":"query","description":"Fields to select (comma separated). Use + prefix to include hidden fields. Example: name,status,+holders","schema":{"type":"string"}},{"name":"populate","required":false,"in":"query","description":"Relations to populate (comma separated). Example: wallet,token","schema":{"type":"string"}}],"responses":{"200":{"description":""}},"tags":["prizes"]}}}}
```

## POST /api/prizes

> Seed a Bound Bunny prize into inventory (admin)

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"servers":[{"url":"https://api.radfi.co","description":"Production"}],"security":[{"bearer":[]}],"components":{"securitySchemes":{"bearer":{"scheme":"bearer","bearerFormat":"JWT","type":"http"}},"schemas":{"SeedPrizeDto":{"type":"object","properties":{"prizeNumber":{"type":"number","description":"Prize number (1..100)"},"name":{"type":"string"},"inscriptionId":{"type":"string","description":"Ordinal inscription id"},"controllingUtxo":{"type":"string","description":"Treasury UTXO holding the inscription (txid:vout)"}},"required":["prizeNumber","name","inscriptionId","controllingUtxo"]}}},"paths":{"/api/prizes":{"post":{"operationId":"PrizeController_seed","summary":"Seed a Bound Bunny prize into inventory (admin)","parameters":[],"requestBody":{"required":true,"content":{"application/json":{"schema":{"$ref":"#/components/schemas/SeedPrizeDto"}}}},"responses":{"201":{"description":""}},"tags":["prizes"]}}}}
```


# Models

## The CreateTransactionDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"CreateTransactionDto":{"type":"object","properties":{"type":{"type":"string","enum":["renew-utxo","withdraw","init-liquidity-pool","provide-liquidity","increase-liquidity","swap","withdraw-liquidity","collect-fee","claim-referral-rewards","claim-receipt","satflow-list","satflow-cancel","satflow-buy"]},"params":{"type":"object"}},"required":["type","params"]}}}}
```

## The SignTransactionParamsDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"SignTransactionParamsDto":{"type":"object","properties":{"userAddress":{"type":"string"},"signedBase64Tx":{"type":"string"}},"required":["userAddress","signedBase64Tx"]}}}}
```

## The SignTransactionDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"SignTransactionDto":{"type":"object","properties":{"type":{"type":"string","enum":["renew-utxo","withdraw","init-liquidity-pool","provide-liquidity","increase-liquidity","swap","withdraw-liquidity","collect-fee","claim-referral-rewards","claim-receipt","satflow-list","satflow-cancel","satflow-buy"]},"params":{"$ref":"#/components/schemas/SignTransactionParamsDto"}},"required":["type","params"]},"SignTransactionParamsDto":{"type":"object","properties":{"userAddress":{"type":"string"},"signedBase64Tx":{"type":"string"}},"required":["userAddress","signedBase64Tx"]}}}}
```

## The ResponseMetaData object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"ResponseMetaData":{"type":"object","properties":{"totalItems":{"type":"number"},"currentPage":{"type":"number"},"pageSize":{"type":"number"},"totalPages":{"type":"number"}},"required":["totalItems","currentPage","pageSize","totalPages"]}}}}
```

## The BaseResponseDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"BaseResponseDto":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"data":{"type":"object"},"metaData":{"$ref":"#/components/schemas/ResponseMetaData"}},"required":["code","message","data","metaData"]},"ResponseMetaData":{"type":"object","properties":{"totalItems":{"type":"number"},"currentPage":{"type":"number"},"pageSize":{"type":"number"},"totalPages":{"type":"number"}},"required":["totalItems","currentPage","pageSize","totalPages"]}}}}
```

## The WalletEntity object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"WalletEntity":{"type":"object","properties":{"tradingAddress":{"type":"string"},"userAddress":{"type":"string"},"userPublicKey":{"type":"string"},"requiredSignNumber":{"type":"number"},"pubKeysNum":{"type":"number"}},"required":["tradingAddress","userAddress","userPublicKey","requiredSignNumber","pubKeysNum"]}}}}
```

## The AddWalletDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"AddWalletDto":{"type":"object","properties":{"chain":{"type":"string","enum":["bitcoin","evm","solana"],"description":"Blockchain network of the wallet"},"provider":{"type":"string","description":"Wallet provider / software used"},"publicKey":{"type":"string","description":"Public key of the wallet"},"message":{"type":"string","description":"Message (timestamp in ms) signed by the wallet"},"signature":{"type":"string","description":"Signature of the message produced by the wallet"},"userAddress":{"type":"string","description":"Wallet address (required for BTC; derived from signature for EVM/SOL)"},"isDefault":{"type":"boolean","description":"Mark this wallet as the default for its chain"}},"required":["chain","provider","publicKey","message","signature"]}}}}
```

## The ImportPendingTxDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"ImportPendingTxDto":{"type":"object","properties":{"externalAddress":{"type":"string"},"txid":{"type":"string","description":"Tx id"},"receiverAddress":{"type":"string","description":"Receiver address"}},"required":["externalAddress","txid","receiverAddress"]}}}}
```

## The WalletUpdateDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"WalletUpdateDto":{"type":"object","properties":{"userAddress":{"type":"string"},"type":{"type":"string"},"isStandard":{"type":"boolean"}},"required":["userAddress","type","isStandard"]}}}}
```

## The WalletBulkUpdateDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"WalletBulkUpdateDto":{"type":"object","properties":{"wallets":{"type":"array","items":{"$ref":"#/components/schemas/WalletUpdateDto"}}},"required":["wallets"]},"WalletUpdateDto":{"type":"object","properties":{"userAddress":{"type":"string"},"type":{"type":"string"},"isStandard":{"type":"boolean"}},"required":["userAddress","type","isStandard"]}}}}
```

## The UpdateWalletDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"UpdateWalletDto":{"type":"object","properties":{"isDefault":{"type":"boolean"}}}}}}
```

## The PoolMigrationDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"PoolMigrationDto":{"type":"object","properties":{"token0Id":{"type":"string","description":"The id of the token0"},"token1Id":{"type":"string","description":"The id of the token1"},"fee":{"type":"number","description":"The fee of the pool"},"scVersion":{"type":"string","description":"The sc version"}},"required":["token0Id","token1Id","fee","scVersion"]}}}}
```

## The MigratePoolDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"MigratePoolDto":{"type":"object","properties":{"payFeeAddress":{"type":"string","description":"The address to pay the fee"},"pools":{"description":"List of pools to migrate, if not exist, will migrate all","type":"array","items":{"$ref":"#/components/schemas/PoolMigrationDto"}},"migrateVersion":{"type":"number","description":"The migration version"},"type":{"type":"string","description":"The additional data"},"feeRate":{"type":"number","description":"The fee rate"}},"required":["payFeeAddress","migrateVersion","type","feeRate"]},"PoolMigrationDto":{"type":"object","properties":{"token0Id":{"type":"string","description":"The id of the token0"},"token1Id":{"type":"string","description":"The id of the token1"},"fee":{"type":"number","description":"The fee of the pool"},"scVersion":{"type":"string","description":"The sc version"}},"required":["token0Id","token1Id","fee","scVersion"]}}}}
```

## The BroadcastMigrationDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"BroadcastMigrationDto":{"type":"object","properties":{"txHex":{"type":"string","description":"The tx hex"},"type":{"type":"string","description":"The tx id"},"migrateVersion":{"type":"number","description":"The migration version"}},"required":["txHex","type","migrateVersion"]}}}}
```

## The CompleteMigrationDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"CompleteMigrationDto":{"type":"object","properties":{"pools":{"description":"List of pools to migrate, if not exist, will migrate all","type":"array","items":{"$ref":"#/components/schemas/PoolMigrationDto"}},"migrateVersion":{"type":"number","description":"The migration times"}},"required":["migrateVersion"]},"PoolMigrationDto":{"type":"object","properties":{"token0Id":{"type":"string","description":"The id of the token0"},"token1Id":{"type":"string","description":"The id of the token1"},"fee":{"type":"number","description":"The fee of the pool"},"scVersion":{"type":"string","description":"The sc version"}},"required":["token0Id","token1Id","fee","scVersion"]}}}}
```

## The TokenSocial object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"TokenSocial":{"type":"object","properties":{}}}}}
```

## The TokenEntity object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"TokenEntity":{"type":"object","properties":{"source":{"type":"string"},"chainName":{"type":"string"},"chainId":{"type":"string"},"symbol":{"type":"string"},"rune":{"type":"string"},"spacedRune":{"type":"string"},"decimals":{"type":"number"},"tokenAddress":{"type":"string"},"mainTokenAddress":{"type":"string"},"tokenAddressV4":{"type":"string"},"mainTokenAddressV4":{"type":"string"},"tokenId":{"type":"string"},"price":{"type":"number"},"priceInSats":{"type":"number"},"priceChange24h":{"type":"number"},"isTest":{"type":"boolean"},"updatedBy":{"type":"string"},"displayTicker":{"type":"string"},"supply":{"type":"string"},"premine":{"type":"string"},"social":{"$ref":"#/components/schemas/TokenSocial"},"volume24h":{"type":"number"},"volume24hInSats":{"type":"number"},"volume7d":{"type":"number"},"volume7dInSats":{"type":"number"},"volumeAllTime":{"type":"number"},"volumeAllTimeInSats":{"type":"number"},"marketCap":{"type":"number"},"marketCapInSats":{"type":"number"},"hasPool":{"type":"boolean"}},"required":["source","chainName","chainId","symbol","rune","spacedRune","decimals","tokenAddress","mainTokenAddress","tokenAddressV4","mainTokenAddressV4","tokenId","price","priceInSats","priceChange24h","isTest","updatedBy","displayTicker","supply","premine","social","volume24h","volume24hInSats","volume7d","volume7dInSats","volumeAllTime","volumeAllTimeInSats","marketCap","marketCapInSats","hasPool"]},"TokenSocial":{"type":"object","properties":{}}}}}
```

## The TokenResponseDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"TokenResponseDto":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"data":{"type":"array","items":{"$ref":"#/components/schemas/TokenEntity"}},"metaData":{"$ref":"#/components/schemas/ResponseMetaData"}},"required":["code","message","data","metaData"]},"TokenEntity":{"type":"object","properties":{"source":{"type":"string"},"chainName":{"type":"string"},"chainId":{"type":"string"},"symbol":{"type":"string"},"rune":{"type":"string"},"spacedRune":{"type":"string"},"decimals":{"type":"number"},"tokenAddress":{"type":"string"},"mainTokenAddress":{"type":"string"},"tokenAddressV4":{"type":"string"},"mainTokenAddressV4":{"type":"string"},"tokenId":{"type":"string"},"price":{"type":"number"},"priceInSats":{"type":"number"},"priceChange24h":{"type":"number"},"isTest":{"type":"boolean"},"updatedBy":{"type":"string"},"displayTicker":{"type":"string"},"supply":{"type":"string"},"premine":{"type":"string"},"social":{"$ref":"#/components/schemas/TokenSocial"},"volume24h":{"type":"number"},"volume24hInSats":{"type":"number"},"volume7d":{"type":"number"},"volume7dInSats":{"type":"number"},"volumeAllTime":{"type":"number"},"volumeAllTimeInSats":{"type":"number"},"marketCap":{"type":"number"},"marketCapInSats":{"type":"number"},"hasPool":{"type":"boolean"}},"required":["source","chainName","chainId","symbol","rune","spacedRune","decimals","tokenAddress","mainTokenAddress","tokenAddressV4","mainTokenAddressV4","tokenId","price","priceInSats","priceChange24h","isTest","updatedBy","displayTicker","supply","premine","social","volume24h","volume24hInSats","volume7d","volume7dInSats","volumeAllTime","volumeAllTimeInSats","marketCap","marketCapInSats","hasPool"]},"TokenSocial":{"type":"object","properties":{}},"ResponseMetaData":{"type":"object","properties":{"totalItems":{"type":"number"},"currentPage":{"type":"number"},"pageSize":{"type":"number"},"totalPages":{"type":"number"}},"required":["totalItems","currentPage","pageSize","totalPages"]}}}}
```

## The TokenGetDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"TokenGetDto":{"type":"object","properties":{"runes":{"type":"array","items":{"type":"string"}}},"required":["runes"]}}}}
```

## The BlackListActions object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"BlackListActions":{"type":"object","properties":{"withdraw":{"type":"boolean","description":"Whether the user can withdraw"},"init-liquidity-pool":{"type":"boolean","description":"Whether the user can init LP"},"provide-liquidity":{"type":"boolean","description":"Whether the user can provide liquidity"},"increase-liquidity":{"type":"boolean","description":"Whether the user can increase liquidity"},"swap":{"type":"boolean","description":"Whether the user can swap"},"withdraw-liquidity":{"type":"boolean","description":"Whether the user can withdraw liquidity"},"collect-fee":{"type":"boolean","description":"Whether the user can collect fee"},"claim-diamond-hand-rewards":{"type":"boolean","description":"Whether the user can claim diamond hand rewards"}},"required":["withdraw","init-liquidity-pool","provide-liquidity","increase-liquidity","swap","withdraw-liquidity","collect-fee","claim-diamond-hand-rewards"]}}}}
```

## The BlackListCreateDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"BlackListCreateDto":{"type":"object","properties":{"tradingAddress":{"type":"string","description":"The trading address of the user"},"actions":{"description":"The actions of the user","allOf":[{"$ref":"#/components/schemas/BlackListActions"}]}},"required":["tradingAddress","actions"]},"BlackListActions":{"type":"object","properties":{"withdraw":{"type":"boolean","description":"Whether the user can withdraw"},"init-liquidity-pool":{"type":"boolean","description":"Whether the user can init LP"},"provide-liquidity":{"type":"boolean","description":"Whether the user can provide liquidity"},"increase-liquidity":{"type":"boolean","description":"Whether the user can increase liquidity"},"swap":{"type":"boolean","description":"Whether the user can swap"},"withdraw-liquidity":{"type":"boolean","description":"Whether the user can withdraw liquidity"},"collect-fee":{"type":"boolean","description":"Whether the user can collect fee"},"claim-diamond-hand-rewards":{"type":"boolean","description":"Whether the user can claim diamond hand rewards"}},"required":["withdraw","init-liquidity-pool","provide-liquidity","increase-liquidity","swap","withdraw-liquidity","collect-fee","claim-diamond-hand-rewards"]}}}}
```

## The EtchEntity object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"EtchEntity":{"type":"object","properties":{}}}}}
```

## The ValidateEtchRuneDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"ValidateEtchRuneDto":{"type":"object","properties":{"rune":{"type":"string","description":"Rune name with or without spacers (•)"}},"required":["rune"]}}}}
```

## The FetchEtchAddressDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"FetchEtchAddressDto":{"type":"object","properties":{"runeName":{"type":"string"},"inscriptionType":{"type":"string"},"inscriptionContent":{"type":"string"}},"required":["runeName","inscriptionType","inscriptionContent"]}}}}
```

## The EtchBuildCommitTxDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"EtchBuildCommitTxDto":{"type":"object","properties":{"runeName":{"type":"string"},"inscriptionType":{"type":"string"},"inscriptionContent":{"type":"string"},"userAddress":{"type":"string"},"feeTier":{"type":"number","description":"Fee tier for the pool (5000 = 0.5%, 10000 = 1%, 20000 = 2%, etc.)","minimum":5000,"maximum":100000},"diamondHandRewardRate":{"type":"number","description":"Diamond hands share of the available 66.67% (0-1, e.g., 0.5 = 50% of 66.67% = 33.33% total)","minimum":0,"maximum":1},"blockWithdrawals":{"type":"boolean","description":"Block withdrawals of this token from radFi (only internal transfers allowed)","default":false},"addLpSatsRate":{"type":"number","description":"Percentage of raised funds to allocate to liquidity (0-1, e.g., 0.2 = 20% to LP, 80% to creator)","minimum":0,"maximum":1},"customFeeRate":{"type":"number","description":"Customized fee rate for the etching transaction (>=1)","minimum":1},"isReserve":{"type":"boolean","description":"Reserve to launch the token at a later date","default":false},"userPremineAmount":{"type":"string","description":"Raw rune amount (base units) to transfer to the creator before public distribution. Must fit within the total premine supply."}},"required":["runeName","inscriptionType","inscriptionContent","userAddress","feeTier"]}}}}
```

## The EtchSocialDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"EtchSocialDto":{"type":"object","properties":{"x":{"type":"string"},"telegram":{"type":"string"},"website":{"type":"string"}}}}}}
```

## The SubmitEtchDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"SubmitEtchDto":{"type":"object","properties":{"signedBase64Psbt":{"type":"string"},"runeName":{"type":"string"},"inscriptionType":{"type":"string"},"inscriptionContent":{"type":"string"},"symbol":{"type":"string"},"creator":{"type":"string"},"description":{"type":"string"},"displayTicker":{"type":"string"},"social":{"$ref":"#/components/schemas/EtchSocialDto"}},"required":["signedBase64Psbt","runeName","inscriptionType","inscriptionContent","symbol","creator","description"]},"EtchSocialDto":{"type":"object","properties":{"x":{"type":"string"},"telegram":{"type":"string"},"website":{"type":"string"}}}}}}
```

## The EtchBuildGetRewardDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"EtchBuildGetRewardDto":{"type":"object","properties":{"runeId":{"type":"string"},"userAddress":{"type":"string"}},"required":["runeId","userAddress"]}}}}
```

## The EtchSignAndBroadcastRewardTxDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"EtchSignAndBroadcastRewardTxDto":{"type":"object","properties":{"signedBase64Tx":{"type":"string"},"userAddress":{"type":"string"}},"required":["signedBase64Tx","userAddress"]}}}}
```

## The EtchUpdateDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"EtchUpdateDto":{"type":"object","properties":{"social":{"$ref":"#/components/schemas/EtchSocialDto"}}},"EtchSocialDto":{"type":"object","properties":{"x":{"type":"string"},"telegram":{"type":"string"},"website":{"type":"string"}}}}}}
```

## The EtchBuildOpenMintDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"EtchBuildOpenMintDto":{"type":"object","properties":{"runeId":{"type":"string"},"userAddress":{"type":"string"}},"required":["runeId"]}}}}
```

## The VMTransactionEntity object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"VMTransactionEntity":{"type":"object","properties":{"txId":{"type":"string"},"replaceTxId":{"type":"string"},"state":{"type":"string"},"runeId":{"type":"string"},"replacedRequestsCount":{"type":"number"},"requestsInMempoolCount":{"type":"number"},"requestsInBlocksCount":{"type":"number"},"satsPerNewRequest":{"type":"number"},"satsNewRequests":{"type":"number"},"satsPreviousRequests":{"type":"number"},"tradingAddress":{"type":"string"},"base64Tx":{"type":"string"},"timestamp":{"type":"number"},"transferBtcTxId":{"type":"string"},"transferBtcAt":{"type":"number"},"distributeRuneTxId":{"type":"string"},"distributeRuneAt":{"type":"number"},"refundTxId":{"type":"string"},"isRefundable":{"type":"boolean"},"vmDistributionId":{"type":"string"}},"required":["txId","replaceTxId","state","runeId","replacedRequestsCount","requestsInMempoolCount","requestsInBlocksCount","satsPerNewRequest","satsNewRequests","satsPreviousRequests","tradingAddress","base64Tx","timestamp","transferBtcTxId","transferBtcAt","distributeRuneTxId","distributeRuneAt","refundTxId","isRefundable","vmDistributionId"]}}}}
```

## The MintDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"MintDto":{"type":"object","properties":{"userAddress":{"type":"string"},"runeId":{"type":"string"},"requestCount":{"type":"number"},"satsNewRequests":{"type":"number"}},"required":["userAddress","runeId","requestCount","satsNewRequests"]}}}}
```

## The RequestClaimDiamondHandRewardsDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"RequestClaimDiamondHandRewardsDto":{"type":"object","properties":{"userAddress":{"type":"string"},"runeId":{"type":"string"}},"required":["userAddress","runeId"]}}}}
```

## The SignAndBroadcastRewardsDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"SignAndBroadcastRewardsDto":{"type":"object","properties":{"signedBase64Tx":{"type":"string"},"userAddress":{"type":"string"},"runeId":{"type":"string"}},"required":["signedBase64Tx","userAddress","runeId"]}}}}
```

## The ClaimBuildDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"ClaimBuildDto":{"type":"object","properties":{"payFeeAddress":{"type":"string","description":"Referrer's 2-of-2 trading wallet address. Funds the claim tx (fee + dust) AND receives the claimed bUSD. Must be the calling referrer's own `tradingAddress` — the BE validates this against `walletService` before building."}},"required":["payFeeAddress"]}}}}
```

## The ClaimSignDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"ClaimSignDto":{"type":"object","properties":{"signedBase64Tx":{"type":"string","description":"Base64-encoded PSBT signed by the referrer"}},"required":["signedBase64Tx"]}}}}
```

## The ChangePasswordDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"ChangePasswordDto":{"type":"object","properties":{"sessionId":{"type":"string","description":"Session ID returned by POST /auth/srp/change-password/init"},"clientPublic":{"type":"string","description":"Client ephemeral public key A = g^a mod N (base64)"},"clientProof":{"type":"string","description":"Client proof M1 = H(H(N)⊕H(g) || H(I) || salt || A || B || K) (base64)"},"newSrpSalt":{"type":"string","description":"New SRP salt generated client-side (base64, 32 bytes)"},"newSrpVerifier":{"type":"string","description":"New SRP verifier v = g^x mod N (base64)"},"newEncryptedBlob":{"type":"string","description":"Keystore blob re-encrypted with new derived key (base64, max 13708 chars)"},"newPasswordHint":{"type":"string","description":"New password hint"}},"required":["sessionId","clientPublic","clientProof","newSrpSalt","newSrpVerifier","newEncryptedBlob"]}}}}
```

## The RotateAuthWalletDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"RotateAuthWalletDto":{"type":"object","properties":{"address":{"type":"string","description":"Address of the NEW auth wallet"},"chain":{"type":"string","enum":["evm","solana","bitcoin"]},"publicKey":{"type":"string","description":"Public key of the NEW auth wallet (verifies EVM/Solana signatures)"},"provider":{"type":"string","description":"metamask | phantom | xverse | okx | unisat"},"challengeId":{"type":"string","description":"challengeId from GET /auth/wallet/challenge?address=<newAddress>"},"signature":{"type":"string","description":"NEW wallet signature over the issued server nonce (control proof)"},"encryptedBlob":{"type":"string","description":"Keystore blob re-encrypted client-side with the NEW wallet derivation key"}},"required":["address","chain","publicKey","provider","challengeId","signature","encryptedBlob"]}}}}
```

## The TotpToggleDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"TotpToggleDto":{"type":"object","properties":{}}}}}
```

## The SetAvatarDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"SetAvatarDto":{"type":"object","properties":{"inscriptionId":{"type":"string","description":"The inscriptionId to set as avatar. Pass null or omit to remove the avatar.","nullable":true}}}}}}
```

## The SetDefaultTokenDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"SetDefaultTokenDto":{"type":"object","properties":{"slot":{"type":"string","enum":["usdc"]},"tokenId":{"type":"string"}},"required":["slot","tokenId"]}}}}
```

## The RemoveDefaultTokenDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"RemoveDefaultTokenDto":{"type":"object","properties":{"slot":{"type":"string","enum":["usdc"]},"tokenId":{"type":"string"}},"required":["slot","tokenId"]}}}}
```

## The ChangeEmailInitDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"ChangeEmailInitDto":{"type":"object","properties":{"newEmail":{"type":"string","description":"New email address to associate with the account"}},"required":["newEmail"]}}}}
```

## The ChangeEmailSrpDataDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"ChangeEmailSrpDataDto":{"type":"object","properties":{"newSrpVerifier":{"type":"string","description":"New SRP verifier recomputed as SRP(newEmail, currentPassword)"},"sessionId":{"type":"string","description":"SRP session ID from the init response"},"clientPublic":{"type":"string","description":"SRP client public ephemeral"},"clientProof":{"type":"string","description":"SRP client proof"}},"required":["newSrpVerifier","sessionId","clientPublic","clientProof"]}}}}
```

## The ChangeEmailWebAuthnAssertionDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"ChangeEmailWebAuthnAssertionDto":{"type":"object","properties":{"authenticatorData":{"type":"string","description":"Base64-encoded authenticator data"},"clientDataJson":{"type":"string","description":"Base64-encoded client data JSON"},"signature":{"type":"string","description":"Base64-encoded DER signature"}},"required":["authenticatorData","clientDataJson","signature"]}}}}
```

## The ChangeEmailPasskeyDataDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"ChangeEmailPasskeyDataDto":{"type":"object","properties":{"credentialId":{"type":"string","description":"Credential ID of the registered passkey"},"challengeId":{"type":"string","description":"Challenge ID from the init response"},"webauthnAssertion":{"description":"WebAuthn assertion from the passkey device","allOf":[{"$ref":"#/components/schemas/ChangeEmailWebAuthnAssertionDto"}]}},"required":["credentialId","challengeId","webauthnAssertion"]},"ChangeEmailWebAuthnAssertionDto":{"type":"object","properties":{"authenticatorData":{"type":"string","description":"Base64-encoded authenticator data"},"clientDataJson":{"type":"string","description":"Base64-encoded client data JSON"},"signature":{"type":"string","description":"Base64-encoded DER signature"}},"required":["authenticatorData","clientDataJson","signature"]}}}}
```

## The ChangeEmailVerifyDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"ChangeEmailVerifyDto":{"type":"object","properties":{"token":{"type":"string","description":"Verification token from the change-email email link"},"email":{"type":"string","description":"New email address being verified (cross-verified against the token)"},"srpData":{"description":"SRP re-auth data — required when the account has an SRP keystore","allOf":[{"$ref":"#/components/schemas/ChangeEmailSrpDataDto"}]},"passkeyData":{"description":"Passkey re-auth data — required when the account has a passkey keystore","allOf":[{"$ref":"#/components/schemas/ChangeEmailPasskeyDataDto"}]}},"required":["token","email"]},"ChangeEmailSrpDataDto":{"type":"object","properties":{"newSrpVerifier":{"type":"string","description":"New SRP verifier recomputed as SRP(newEmail, currentPassword)"},"sessionId":{"type":"string","description":"SRP session ID from the init response"},"clientPublic":{"type":"string","description":"SRP client public ephemeral"},"clientProof":{"type":"string","description":"SRP client proof"}},"required":["newSrpVerifier","sessionId","clientPublic","clientProof"]},"ChangeEmailPasskeyDataDto":{"type":"object","properties":{"credentialId":{"type":"string","description":"Credential ID of the registered passkey"},"challengeId":{"type":"string","description":"Challenge ID from the init response"},"webauthnAssertion":{"description":"WebAuthn assertion from the passkey device","allOf":[{"$ref":"#/components/schemas/ChangeEmailWebAuthnAssertionDto"}]}},"required":["credentialId","challengeId","webauthnAssertion"]},"ChangeEmailWebAuthnAssertionDto":{"type":"object","properties":{"authenticatorData":{"type":"string","description":"Base64-encoded authenticator data"},"clientDataJson":{"type":"string","description":"Base64-encoded client data JSON"},"signature":{"type":"string","description":"Base64-encoded DER signature"}},"required":["authenticatorData","clientDataJson","signature"]}}}}
```

## The RequestLinkDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"RequestLinkDto":{"type":"object","properties":{"email":{"type":"string","description":"Email of the account Device B wants to link to"},"pubKeyB":{"type":"string","description":"Device B ephemeral ECDH public key (base64 or hex)"}},"required":["email","pubKeyB"]}}}}
```

## The ApproveLinkDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"ApproveLinkDto":{"type":"object","properties":{"otp":{"type":"string","description":"OTP from the request-link session"},"encryptedForB":{"type":"string","description":"AES-GCM encrypted mnemonic, key derived via ECDH (base64)"},"pubKeyA":{"type":"string","description":"Device A ephemeral ECDH public key (base64 or hex)"}},"required":["otp","encryptedForB","pubKeyA"]}}}}
```

## The ConfirmLinkDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"ConfirmLinkDto":{"type":"object","properties":{"otp":{"type":"string","description":"OTP from the request-link session"},"email":{"type":"string","description":"Email address — must match the account that owns the OTP session"},"challengeId":{"type":"string","description":"Challenge ID from GET /auth/webauthn/challenge"},"credentialId":{"type":"string","description":"WebAuthn credential ID"},"webauthnPublicKey":{"type":"string","description":"DER/SPKI public key (base64)"},"attestation":{"type":"string","description":"CBOR attestation object (base64)"},"clientDataJson":{"type":"string","description":"WebAuthn clientDataJSON (base64)"},"authenticatorData":{"type":"string","description":"WebAuthn authenticatorData (base64)"},"encryptedBlob":{"type":"string","description":"Keystore blob encrypted with Device B derived key"},"deviceName":{"type":"string","description":"Human-readable device label (e.g. \"Work Laptop\")"}},"required":["otp","email","challengeId","credentialId","webauthnPublicKey","attestation","clientDataJson","authenticatorData","encryptedBlob"]}}}}
```

## The RequestPasswordHintDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"RequestPasswordHintDto":{"type":"object","properties":{"email":{"type":"string","description":"Email address of the account to send the password hint to"}},"required":["email"]}}}}
```

## The VerifyInscriptionsDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"VerifyInscriptionsDto":{"type":"object","properties":{"inscriptionIds":{"description":"Array of inscription IDs to verify","minItems":1,"maxItems":3,"type":"array","items":{"type":"string"}},"walletAddress":{"type":"string"}},"required":["inscriptionIds"]}}}}
```

## The AuthenticateDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"AuthenticateDto":{"type":"object","properties":{"message":{"type":"string","description":"The message that was signed"},"signature":{"type":"string","description":"The BIP322 signature of the message"},"address":{"type":"string","description":"The Bitcoin address that signed the message"},"publicKey":{"type":"string","description":"The public key used for signing"}},"required":["message","signature","address","publicKey"]}}}}
```

## The RefreshTokenDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"RefreshTokenDto":{"type":"object","properties":{"refreshToken":{"type":"string","description":"Refresh token to generate new access token"}},"required":["refreshToken"]}}}}
```

## The EmailVerificationRequestDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"EmailVerificationRequestDto":{"type":"object","properties":{"email":{"type":"string","description":"Email address to send OTP to"}},"required":["email"]}}}}
```

## The RegisterPasskeyDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"RegisterPasskeyDto":{"type":"object","properties":{"challengeId":{"type":"string"},"credentialId":{"type":"string"},"webauthnPublicKey":{"type":"string"},"attestation":{"type":"string"},"clientDataJson":{"type":"string"},"authenticatorData":{"type":"string"},"encryptedBlob":{"type":"string"},"deviceName":{"type":"string"}},"required":["challengeId","credentialId","webauthnPublicKey","attestation","clientDataJson","authenticatorData","encryptedBlob"]}}}}
```

## The RegisterSrpDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"RegisterSrpDto":{"type":"object","properties":{"srpSalt":{"type":"string","description":"Random salt generated client-side (base64, 32 bytes)"},"srpVerifier":{"type":"string","description":"SRP verifier v = g^x mod N (base64), x = H(salt || H(email:password))"},"encryptedBlob":{"type":"string"},"passwordHint":{"type":"string"}},"required":["srpSalt","srpVerifier","encryptedBlob"]}}}}
```

## The RegisterWalletAuthDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"RegisterWalletAuthDto":{"type":"object","properties":{"address":{"type":"string"},"chain":{"type":"string","enum":["evm","solana","bitcoin"]},"publicKey":{"type":"string"},"provider":{"type":"string"},"challengeId":{"type":"string"},"signature":{"type":"string"},"encryptedBlob":{"type":"string"}},"required":["address","chain","publicKey","provider","challengeId","signature","encryptedBlob"]}}}}
```

## The RegisterBtcWalletDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"RegisterBtcWalletDto":{"type":"object","properties":{"publicKey":{"type":"string"},"address":{"type":"string"},"message":{"type":"string"},"signature":{"type":"string"}},"required":["publicKey","address","message","signature"]}}}}
```

## The RegisterEvmWalletDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"RegisterEvmWalletDto":{"type":"object","properties":{"publicKey":{"type":"string"},"message":{"type":"string"},"signature":{"type":"string"}},"required":["publicKey","message","signature"]}}}}
```

## The RegisterSolWalletDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"RegisterSolWalletDto":{"type":"object","properties":{"publicKey":{"type":"string"},"message":{"type":"string"},"signature":{"type":"string"}},"required":["publicKey","message","signature"]}}}}
```

## The RegisterWalletsDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"RegisterWalletsDto":{"type":"object","properties":{"btc":{"$ref":"#/components/schemas/RegisterBtcWalletDto"},"evm":{"$ref":"#/components/schemas/RegisterEvmWalletDto"},"sol":{"$ref":"#/components/schemas/RegisterSolWalletDto"}},"required":["btc","evm","sol"]},"RegisterBtcWalletDto":{"type":"object","properties":{"publicKey":{"type":"string"},"address":{"type":"string"},"message":{"type":"string"},"signature":{"type":"string"}},"required":["publicKey","address","message","signature"]},"RegisterEvmWalletDto":{"type":"object","properties":{"publicKey":{"type":"string"},"message":{"type":"string"},"signature":{"type":"string"}},"required":["publicKey","message","signature"]},"RegisterSolWalletDto":{"type":"object","properties":{"publicKey":{"type":"string"},"message":{"type":"string"},"signature":{"type":"string"}},"required":["publicKey","message","signature"]}}}}
```

## The RegisterDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"RegisterDto":{"type":"object","properties":{"email":{"type":"string"},"otp":{"type":"string"},"authType":{"type":"string","enum":["passkey","srp","wallet"]},"passkey":{"$ref":"#/components/schemas/RegisterPasskeyDto"},"srpData":{"$ref":"#/components/schemas/RegisterSrpDto"},"walletAuth":{"$ref":"#/components/schemas/RegisterWalletAuthDto"},"wallets":{"$ref":"#/components/schemas/RegisterWalletsDto"},"referralCode":{"type":"string","description":"Referral code (base64url-encoded referrer accountId). Throws if invalid."}},"required":["email","otp","authType","wallets"]},"RegisterPasskeyDto":{"type":"object","properties":{"challengeId":{"type":"string"},"credentialId":{"type":"string"},"webauthnPublicKey":{"type":"string"},"attestation":{"type":"string"},"clientDataJson":{"type":"string"},"authenticatorData":{"type":"string"},"encryptedBlob":{"type":"string"},"deviceName":{"type":"string"}},"required":["challengeId","credentialId","webauthnPublicKey","attestation","clientDataJson","authenticatorData","encryptedBlob"]},"RegisterSrpDto":{"type":"object","properties":{"srpSalt":{"type":"string","description":"Random salt generated client-side (base64, 32 bytes)"},"srpVerifier":{"type":"string","description":"SRP verifier v = g^x mod N (base64), x = H(salt || H(email:password))"},"encryptedBlob":{"type":"string"},"passwordHint":{"type":"string"}},"required":["srpSalt","srpVerifier","encryptedBlob"]},"RegisterWalletAuthDto":{"type":"object","properties":{"address":{"type":"string"},"chain":{"type":"string","enum":["evm","solana","bitcoin"]},"publicKey":{"type":"string"},"provider":{"type":"string"},"challengeId":{"type":"string"},"signature":{"type":"string"},"encryptedBlob":{"type":"string"}},"required":["address","chain","publicKey","provider","challengeId","signature","encryptedBlob"]},"RegisterWalletsDto":{"type":"object","properties":{"btc":{"$ref":"#/components/schemas/RegisterBtcWalletDto"},"evm":{"$ref":"#/components/schemas/RegisterEvmWalletDto"},"sol":{"$ref":"#/components/schemas/RegisterSolWalletDto"}},"required":["btc","evm","sol"]},"RegisterBtcWalletDto":{"type":"object","properties":{"publicKey":{"type":"string"},"address":{"type":"string"},"message":{"type":"string"},"signature":{"type":"string"}},"required":["publicKey","address","message","signature"]},"RegisterEvmWalletDto":{"type":"object","properties":{"publicKey":{"type":"string"},"message":{"type":"string"},"signature":{"type":"string"}},"required":["publicKey","message","signature"]},"RegisterSolWalletDto":{"type":"object","properties":{"publicKey":{"type":"string"},"message":{"type":"string"},"signature":{"type":"string"}},"required":["publicKey","message","signature"]}}}}
```

## The WebAuthnAssertionDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"WebAuthnAssertionDto":{"type":"object","properties":{"authenticatorData":{"type":"string"},"clientDataJson":{"type":"string"},"signature":{"type":"string"}},"required":["authenticatorData","clientDataJson","signature"]}}}}
```

## The LoginDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"LoginDto":{"type":"object","properties":{"credentialId":{"type":"string"},"challengeId":{"type":"string"},"webauthnAssertion":{"$ref":"#/components/schemas/WebAuthnAssertionDto"}},"required":["credentialId","challengeId","webauthnAssertion"]},"WebAuthnAssertionDto":{"type":"object","properties":{"authenticatorData":{"type":"string"},"clientDataJson":{"type":"string"},"signature":{"type":"string"}},"required":["authenticatorData","clientDataJson","signature"]}}}}
```

## The SrpInitDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"SrpInitDto":{"type":"object","properties":{"email":{"type":"string","description":"Account email"}},"required":["email"]}}}}
```

## The SrpVerifyDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"SrpVerifyDto":{"type":"object","properties":{"sessionId":{"type":"string","description":"Session ID returned by srp/init"},"clientPublic":{"type":"string","description":"Client ephemeral public key A = g^a mod N (base64)"},"clientProof":{"type":"string","description":"Client proof M1 = H(H(N)⊕H(g) || H(I) || salt || A || B || K) (base64)"}},"required":["sessionId","clientPublic","clientProof"]}}}}
```

## The WalletLoginDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"WalletLoginDto":{"type":"object","properties":{"address":{"type":"string","description":"Registered auth wallet address"},"challengeId":{"type":"string","description":"challengeId from GET /auth/wallet/challenge"},"signature":{"type":"string","description":"Wallet signature over the issued server nonce"}},"required":["address","challengeId","signature"]}}}}
```

## The TotpVerifyDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"TotpVerifyDto":{"type":"object","properties":{}}}}}
```

## The ISignedFields object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"ISignedFields":{"type":"object","properties":{"key":{"type":"string"},"Policy":{"type":"string"},"bucket":{"type":"string"},"X-Amz-Date":{"type":"string"},"Content-Type":{"type":"string"},"X-Amz-Algorithm":{"type":"string"},"X-Amz-Signature":{"type":"string"},"X-Amz-Credential":{"type":"string"}},"required":["key","Policy","bucket","X-Amz-Date","Content-Type","X-Amz-Algorithm","X-Amz-Signature","X-Amz-Credential"]}}}}
```

## The ISignedUrl object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"ISignedUrl":{"type":"object","properties":{"url":{"type":"string"},"fields":{"$ref":"#/components/schemas/ISignedFields"}},"required":["url","fields"]},"ISignedFields":{"type":"object","properties":{"key":{"type":"string"},"Policy":{"type":"string"},"bucket":{"type":"string"},"X-Amz-Date":{"type":"string"},"Content-Type":{"type":"string"},"X-Amz-Algorithm":{"type":"string"},"X-Amz-Signature":{"type":"string"},"X-Amz-Credential":{"type":"string"}},"required":["key","Policy","bucket","X-Amz-Date","Content-Type","X-Amz-Algorithm","X-Amz-Signature","X-Amz-Credential"]}}}}
```

## The RuneEtchMediaEntity object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"RuneEtchMediaEntity":{"type":"object","properties":{"key":{"type":"string"},"url":{"type":"string"},"signedUrl":{"$ref":"#/components/schemas/ISignedUrl"},"fileName":{"type":"string"},"mimeType":{"type":"string"},"size":{"type":"number"}},"required":["key","url","signedUrl","fileName","mimeType","size"]},"ISignedUrl":{"type":"object","properties":{"url":{"type":"string"},"fields":{"$ref":"#/components/schemas/ISignedFields"}},"required":["url","fields"]},"ISignedFields":{"type":"object","properties":{"key":{"type":"string"},"Policy":{"type":"string"},"bucket":{"type":"string"},"X-Amz-Date":{"type":"string"},"Content-Type":{"type":"string"},"X-Amz-Algorithm":{"type":"string"},"X-Amz-Signature":{"type":"string"},"X-Amz-Credential":{"type":"string"}},"required":["key","Policy","bucket","X-Amz-Date","Content-Type","X-Amz-Algorithm","X-Amz-Signature","X-Amz-Credential"]}}}}
```

## The ItemPresignedUrlDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"ItemPresignedUrlDto":{"type":"object","properties":{"filename":{"type":"string"}},"required":["filename"]}}}}
```

## The PresignedUrlDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"PresignedUrlDto":{"type":"object","properties":{"files":{"type":"array","items":{"$ref":"#/components/schemas/ItemPresignedUrlDto"}},"userAddress":{"type":"string"}},"required":["files","userAddress"]},"ItemPresignedUrlDto":{"type":"object","properties":{"filename":{"type":"string"}},"required":["filename"]}}}}
```

## The CreatePolicySignatureDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"CreatePolicySignatureDto":{"type":"object","properties":{"message":{"type":"string"},"userAddress":{"type":"string"},"signature":{"type":"string"}},"required":["message","userAddress","signature"]}}}}
```

## The SettingUpdateDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"SettingUpdateDto":{"type":"object","properties":{"key":{"type":"string","enum":["virtual_mint","transaction","pool","sodax","keystore","bound_lending","satflow","email","message_processor","email_outbox","app","permission","referral","ordinal_drop"]},"data":{"type":"object"}},"required":["key","data"]}}}}
```

## The CreateRefundDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"CreateRefundDto":{"type":"object","properties":{"type":{"type":"string","enum":["virtual_mint"]},"runeIds":{"description":"require if refund type is \"virtual_mint\"","type":"array","items":{"type":"string"}},"userAddress":{"type":"string"}},"required":["type","userAddress"]}}}}
```

## The BroadcastRefundDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"BroadcastRefundDto":{"type":"object","properties":{"base64Psbt":{"type":"string"},"userAddress":{"type":"string"}},"required":["base64Psbt","userAddress"]}}}}
```

## The AdminCreateRefundDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"AdminCreateRefundDto":{"type":"object","properties":{"runeIds":{"type":"array","items":{"type":"string"}},"userAddresses":{"type":"array","items":{"type":"string"}},"isBroadcast":{"type":"boolean"},"states":{"type":"array","items":{"type":"string"}}},"required":["runeIds","userAddresses","isBroadcast","states"]}}}}
```

## The EventSourceRegisterDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"EventSourceRegisterDto":{"type":"object","properties":{"userAddress":{"type":"string"}},"required":["userAddress"]}}}}
```

## The ActivityFeedItemDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"ActivityFeedItemDto":{"type":"object","properties":{"source":{"type":"string","enum":["amm","sodax"]},"pair":{"type":"string"},"amountSold":{"type":"string"},"tokenSoldSymbol":{"type":"string","description":"Symbol of token sold (for display)"},"amountReceived":{"type":"string"},"tokenReceivedSymbol":{"type":"string","description":"Symbol of token received (for display)"},"tradeValueUsd":{"type":"number"},"trader":{"type":"string"},"time":{"type":"number"},"txId":{"type":"string"}},"required":["source","pair","amountSold","tokenSoldSymbol","amountReceived","tokenReceivedSymbol","tradeValueUsd","trader","time","txId"]}}}}
```

## The ActivityFeedResponseDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"ActivityFeedResponseDto":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"data":{"type":"array","items":{"$ref":"#/components/schemas/ActivityFeedItemDto"}},"metaData":{"$ref":"#/components/schemas/ResponseMetaData"}},"required":["code","message","data","metaData"]},"ActivityFeedItemDto":{"type":"object","properties":{"source":{"type":"string","enum":["amm","sodax"]},"pair":{"type":"string"},"amountSold":{"type":"string"},"tokenSoldSymbol":{"type":"string","description":"Symbol of token sold (for display)"},"amountReceived":{"type":"string"},"tokenReceivedSymbol":{"type":"string","description":"Symbol of token received (for display)"},"tradeValueUsd":{"type":"number"},"trader":{"type":"string"},"time":{"type":"number"},"txId":{"type":"string"}},"required":["source","pair","amountSold","tokenSoldSymbol","amountReceived","tokenReceivedSymbol","tradeValueUsd","trader","time","txId"]},"ResponseMetaData":{"type":"object","properties":{"totalItems":{"type":"number"},"currentPage":{"type":"number"},"pageSize":{"type":"number"},"totalPages":{"type":"number"}},"required":["totalItems","currentPage","pageSize","totalPages"]}}}}
```

## The ImportExpiredTxsDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"ImportExpiredTxsDto":{"type":"object","properties":{"expiredTxs":{"description":"Token0 ID","type":"array","items":{"type":"string"}}},"required":["expiredTxs"]}}}}
```

## The CreateSodaxTxDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"CreateSodaxTxDto":{"type":"object","properties":{"type":{"type":"string","enum":["sodax-withdraw","sodax-withdraw-musig2","sodax-solver-swap"]},"params":{"type":"object"}},"required":["type","params"]}}}}
```

## The RelayDataDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"RelayDataDto":{"type":"object","properties":{"address":{"type":"string","description":"Hub destination address"},"payload":{"type":"string","description":"Encoded intent instruction payload"}},"required":["address","payload"]}}}}
```

## The SignSodaxTransactionParamsDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"SignSodaxTransactionParamsDto":{"type":"object","properties":{"userAddress":{"type":"string","description":"Set from JWT token. Do not send from client."},"signedBase64Tx":{"type":"string"},"relayData":{"description":"Relay extra data from SDK createIntent(). Required for BTC relay resubmit.","allOf":[{"$ref":"#/components/schemas/RelayDataDto"}]}},"required":["signedBase64Tx"]},"RelayDataDto":{"type":"object","properties":{"address":{"type":"string","description":"Hub destination address"},"payload":{"type":"string","description":"Encoded intent instruction payload"}},"required":["address","payload"]}}}}
```

## The SignSodaxTransactionDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"SignSodaxTransactionDto":{"type":"object","properties":{"type":{"type":"string","enum":["sodax-withdraw","sodax-withdraw-musig2","sodax-solver-swap"]},"params":{"$ref":"#/components/schemas/SignSodaxTransactionParamsDto"}},"required":["type","params"]},"SignSodaxTransactionParamsDto":{"type":"object","properties":{"userAddress":{"type":"string","description":"Set from JWT token. Do not send from client."},"signedBase64Tx":{"type":"string"},"relayData":{"description":"Relay extra data from SDK createIntent(). Required for BTC relay resubmit.","allOf":[{"$ref":"#/components/schemas/RelayDataDto"}]}},"required":["signedBase64Tx"]},"RelayDataDto":{"type":"object","properties":{"address":{"type":"string","description":"Hub destination address"},"payload":{"type":"string","description":"Encoded intent instruction payload"}},"required":["address","payload"]}}}}
```

## The PartnerTxEntity object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"PartnerTxEntity":{"type":"object","properties":{}}}}}
```

## The SodaxResponseDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"SodaxResponseDto":{"type":"object","properties":{"code":{"type":"string"},"message":{"type":"string"},"data":{"type":"array","items":{"$ref":"#/components/schemas/PartnerTxEntity"}},"metaData":{"$ref":"#/components/schemas/ResponseMetaData"}},"required":["code","message","data","metaData"]},"PartnerTxEntity":{"type":"object","properties":{}},"ResponseMetaData":{"type":"object","properties":{"totalItems":{"type":"number"},"currentPage":{"type":"number"},"pageSize":{"type":"number"},"totalPages":{"type":"number"}},"required":["totalItems","currentPage","pageSize","totalPages"]}}}}
```

## The SignBoundLendingPsbtDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"SignBoundLendingPsbtDto":{"type":"object","properties":{"psbtBase64":{"type":"string","description":"Base64-encoded PSBT with all required signatures except RadFi BE trading wallet co-sign"},"type":{"type":"string","description":"PSBT type: origination, repayment, option_origination, or option_exercise","enum":["origination","repayment","option_origination","option_exercise","order_book_premium"]},"userAccountId":{"type":"string","description":"Borrower's Bound accountId (24-hex Mongo ObjectId). When set on an origination PSBT, the server resolves the borrower's referrer (if any) and enforces that the referrer's address appears as an output receiving a proportional bUSD share of the protocol fee (referralFeeBps / MAX_FEE). Optional — omit to skip referrer validation."},"userTradingAddress":{"type":"string","description":"Trading wallet address of the user who initiates from the FE (borrower for lending, seller for options)"},"solverTradingAddress":{"type":"string","description":"Trading wallet address of the solver/counterparty (lender for lending, buyer for options)"},"borrowerPubkey":{"type":"string","description":"Borrower 33-byte compressed pubkey hex (required for origination/repayment)"},"lenderPubkey":{"type":"string","description":"Lender 33-byte compressed pubkey hex (required for origination/repayment)"},"forfeitureExpiryTimestamp":{"type":"number","description":"Absolute Unix timestamp for forfeiture CLTV leaf (required for origination/repayment)"},"previousLenderPubkey":{"type":"string","description":"Lender 33-byte compressed pubkey hex of the previous loan escrow being rolled over. Required only when the origination PSBT spends a previous escrow input (rollover)."},"previousForfeitureExpiryTimestamp":{"type":"number","description":"Forfeiture CLTV expiry timestamp of the previous loan escrow being rolled over. When set, the origination PSBT is treated as a rollover: input 0 must spend the reconstructed previous escrow (script-path) and the metadata OP_RETURN flag must be 0x04."},"previousLenderTradingAddress":{"type":"string","description":"Previous lender trading wallet address (used in cross-lender rollover to allow the payment output back to the previous lender)."},"sellerPubkey":{"type":"string","description":"Seller 33-byte compressed pubkey hex (required for option_origination/option_exercise)"},"escrowExpiryTimestamp":{"type":"number","description":"Absolute Unix timestamp for escrow CLTV expiry (required for option_origination/option_exercise)"},"hashY":{"type":"string","description":"SHA256 hash Y (32-byte hex) for HTLC escrow (required for option_origination/option_exercise)"}},"required":["psbtBase64","type"]}}}}
```

## The SignBoundLendingPsbtResponseDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"SignBoundLendingPsbtResponseDto":{"type":"object","properties":{"txId":{"type":"string","description":"Transaction ID of the broadcasted transaction"},"hex":{"type":"string","description":"Raw transaction hex"}},"required":["txId","hex"]}}}}
```

## The DownloadHistoryFilterDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"DownloadHistoryFilterDto":{"type":"object","properties":{"chain":{"type":"string","enum":["bitcoin","ethereum"]},"type":{"type":"string","enum":["swap","withdraw_liquidity","collect_fee","init_pool","provide_liquidity","increase_liquidity","withdraw","withdraw_rune_stable_coin","renew_utxo","protocol_collect_fee","splitting_orders","deposit","satflow_list","satflow_cancel","satflow_buy"]},"from":{"type":"string"},"to":{"type":"string"}}}}}}
```

## The CreateDownloadHistoryDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"CreateDownloadHistoryDto":{"type":"object","properties":{"format":{"type":"string","enum":["csv","json","pdf"]},"filter":{"$ref":"#/components/schemas/DownloadHistoryFilterDto"}},"required":["format","filter"]},"DownloadHistoryFilterDto":{"type":"object","properties":{"chain":{"type":"string","enum":["bitcoin","ethereum"]},"type":{"type":"string","enum":["swap","withdraw_liquidity","collect_fee","init_pool","provide_liquidity","increase_liquidity","withdraw","withdraw_rune_stable_coin","renew_utxo","protocol_collect_fee","splitting_orders","deposit","satflow_list","satflow_cancel","satflow_buy"]},"from":{"type":"string"},"to":{"type":"string"}}}}}}
```

## The CreateSatflowTxDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"CreateSatflowTxDto":{"type":"object","properties":{"type":{"type":"string","enum":["satflow-list","satflow-cancel","satflow-buy"]},"userAddress":{"type":"string"},"inscriptionId":{"type":"string"},"priceInSats":{"type":"string"},"orderIds":{"type":"array","items":{"type":"string"}},"orderType":{"type":"string"},"inscriptionIds":{"description":"Inscription IDs to buy","type":"array","items":{"type":"string"}}},"required":["type","userAddress"]}}}}
```

## The SignSatflowTxDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"SignSatflowTxDto":{"type":"object","properties":{"type":{"type":"string","enum":["satflow-list","satflow-cancel","satflow-buy"]},"userAddress":{"type":"string"},"signedPsbt":{"type":"string"},"intentId":{"type":"string"},"signedChallenge":{"type":"string"},"signedSecureListingPsbts":{"type":"array","items":{"type":"string"}},"signedPaymentPrepPsbts":{"type":"array","items":{"type":"string"}},"signedPurchasePsbts":{"type":"array","items":{"type":"string"}},"signedTransferPsbt":{"type":"string"},"feeRate":{"type":"number"}},"required":["type","userAddress","intentId"]}}}}
```

## The RevertSatflowBuyDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"RevertSatflowBuyDto":{"type":"object","properties":{"inscriptionId":{"type":"string","description":"Inscription ID of the BUY intent to revert (FE tracks this, not the server-generated txId)"},"userAddress":{"type":"string"}},"required":["inscriptionId","userAddress"]}}}}
```

## The SeedPrizeDto object

```json
{"openapi":"3.0.0","info":{"title":"Radfi API","version":"1.0"},"components":{"schemas":{"SeedPrizeDto":{"type":"object","properties":{"prizeNumber":{"type":"number","description":"Prize number (1..100)"},"name":{"type":"string"},"inscriptionId":{"type":"string","description":"Ordinal inscription id"},"controllingUtxo":{"type":"string","description":"Treasury UTXO holding the inscription (txid:vout)"}},"required":["prizeNumber","name","inscriptionId","controllingUtxo"]}}}}
```


# Authentication Guide

This comprehensive guide covers the complete authentication system for the radFi backend API, including JWT tokens, BIP322 signature verification, error handling, and frontend integration.

## Overview

The authentication system uses BIP322 signature verification combined with JWT tokens to provide secure, stateless authentication. Users authenticate by signing a message with their Bitcoin wallet, and the system returns JWT access and refresh tokens for subsequent API calls.

### Key Features

* **BIP322 Signature Verification**: Users sign messages with their Bitcoin wallet
* **JWT Token System**: Access tokens (10 minutes) and refresh tokens (7 days)
* **Stateless Authentication**: No server-side session storage required
* **Comprehensive Error Handling**: 8 specific error codes for different failure scenarios
* **Wallet Integration**: Automatic wallet creation during authentication

## Authentication Flow

{% stepper %}
{% step %}

### User Authentication (Initial)

Frontend signs a message with BIP322 and sends the signature and related data to the backend. The backend verifies the BIP322 signature, creates or finds the wallet, generates JWT access and refresh tokens, and returns them to the client.

Request sent from client to API:

* POST /api/auth/authenticate with body:
  * message
  * signature
  * address
  * publicKey

Response includes:

* accessToken (expires in 10 minutes)
* refreshToken (expires in 7 days)
* tradingAddress
* wallet object
  {% endstep %}

{% step %}

### Protected API Calls

For protected endpoints, the frontend includes the access token in the Authorization header:

Authorization: Bearer

The backend validates the JWT and, if valid, processes the request (e.g., creating transactions).
{% endstep %}

{% step %}

### Token Refresh (When Access Token Expires)

When the access token expires, the client sends the refresh token to obtain a new access token:

* POST /api/auth/refresh-token with body:
  * refreshToken

On success, the backend returns a new accessToken (and wallet info).
{% endstep %}
{% endstepper %}

## API Endpoints

### Public Endpoints (No Authentication Required)

| Method | Endpoint                   | Description                                  |
| ------ | -------------------------- | -------------------------------------------- |
| POST   | `/api/auth/authenticate`   | Initial authentication with BIP322 signature |
| POST   | `/api/auth/refresh-token`  | Refresh expired access token                 |
| POST   | `/api/wallets`             | Create new wallet                            |
| GET    | `/api/wallets`             | List wallets                                 |
| GET    | `/api/wallets/details/:id` | Get wallet details                           |

### Protected Endpoints (JWT Required)

| Method | Endpoint                                  | Description                |
| ------ | ----------------------------------------- | -------------------------- |
| POST   | `/api/transactions`                       | Create transaction         |
| POST   | `/api/transactions/sign`                  | Sign transaction           |
| POST   | `/api/etch/commit-tx`                     |                            |
| POST   | `/api/etch/submit-etching`                | Submit rune etching        |
| POST   | `/api/etch/build-get-reward-tx`           |                            |
| POST   | `/api/etch/sign-reward-tx`                |                            |
| POST   | `/api/diamond-hand/request-claim-rewards` | Claim diamond hand rewards |
| POST   | `/api/diamond-hand/sign`                  |                            |
| POST   | `/api/vm-transactions`                    | Create VM transaction      |
| POST   | `/api/vm-transactions/sign`               | Create VM transaction      |
| POST   | `/api/refunds`                            | Create refund              |
| POST   | `/api/refunds/sign`                       | Create refund              |

## Error Codes

### Error Code Structure

All authentication errors follow the pattern `4xxx` where:

* `4` indicates authentication/authorization errors
* `xxx` is a sequential number for specific error types

### Error Response Format

```json
{
    "code": "4xxx",
    "message": "auth.errorType",
    "details": "Human-readable error description",
    "additionalData": {} // Optional additional context
}
```

### Error Codes Reference

| Code | Error Type                       | Description                                      | HTTP Status      |
| ---- | -------------------------------- | ------------------------------------------------ | ---------------- |
| 4002 | Invalid Token                    | JWT token is malformed or cannot be parsed       | 401 Unauthorized |
| 4003 | Token Expired                    | JWT token has passed its expiration time         | 401 Unauthorized |
| 4004 | Token Not Found                  | No Authorization header or Bearer token provided | 401 Unauthorized |
| 4005 | Invalid Refresh Token            | Refresh token is invalid, expired, or wrong type | 400 Bad Request  |
| 4006 | User Not Found                   | JWT is valid but user doesn't exist in database  | 401 Unauthorized |
| 4007 | Signature Verification Failed    | BIP322 signature verification process fails      | 400 Bad Request  |
| 4008 | Wallet Creation Failed           | Wallet creation fails during authentication      | 400 Bad Request  |
| 4009 | Invalid Standard Taproot Address | Address doesn't match expected taproot format    | 400 Bad Request  |

### Detailed Error Descriptions

#### 4002 - Invalid Token

* Message: `auth.invalidToken`
* Details: `Invalid or malformed JWT token`
* When: JWT token is malformed or cannot be parsed
* HTTP Status: 401 Unauthorized

#### 4003 - Token Expired

* Message: `auth.tokenExpired`
* Details: `JWT token has expired`
* When: JWT token has passed its expiration time
* HTTP Status: 401 Unauthorized

#### 4004 - Token Not Found

* Message: `auth.tokenNotFound`
* Details: `JWT token not provided in request`
* When: No Authorization header or Bearer token provided
* HTTP Status: 401 Unauthorized

#### 4005 - Invalid Refresh Token

* Message: `auth.invalidRefreshToken`
* Details: `Invalid or expired refresh token`
* When: Refresh token is invalid, expired, or wrong type
* HTTP Status: 400 Bad Request

#### 4006 - User Not Found

* Message: `auth.userNotFound`
* Details: `User not found for the provided token`
* When: JWT is valid but user doesn't exist in database
* HTTP Status: 401 Unauthorized

#### 4007 - Signature Verification Failed

* Message: `auth.signatureVerificationFailed`
* Details: `BIP322 signature verification failed`
* When: BIP322 signature verification process fails
* HTTP Status: 400 Bad Request

#### 4008 - Wallet Creation Failed

* Message: `auth.walletCreationFailed`
* Details: `Failed to create wallet during authentication`
* When: Wallet creation fails during authentication process
* HTTP Status: 400 Bad Request

#### 4009 - Invalid Standard Taproot Address

* Message: `auth.invalidStandardTaprootAddress`
* Details: `Address is not a valid standard taproot address`
* When: Address doesn't match the expected taproot format
* HTTP Status: 400 Bad Request

## Frontend Implementation

### Header Format

All protected API calls require the following header:

```
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
```

### Authentication APIs

#### API: Initial Authentication

Endpoint: `POST /api/auth/authenticate`\
Purpose: Get initial JWT tokens after BIP322 signature verification

Request Body example:

```json
{
    "message": "1759413612750",
    "signature": "AUE5z8iM+Y3M6ey8j7zluhzf75R...XZ+nQH7lRAQ==",
    "address": "bc1py270r0u9y8248s45tpe479j8w...xspezlzu",
    "publicKey": "0324e27cae4cec8374d1c970caf...7c03"
}
```

Response (Success 200) example:

```json
{
    "success": true,
    "data": {
        "tradingAddress": "bc1p...",
        "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
        "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
        "wallet": {
            "_id": "68e1fc5a73513ef283c17b2f",
            "deletedAt": null,
            "tradingAddress": "bc1p...",
            "userAddress": "bc1pge...",
            "userPublicKey": "4ae4...",
            "requiredSignNumber": 2,
            "pubKeysNum": 2,
            "agreeTerm": false,
            "createdAt": 1759640666632,
            "updatedAt": 1759640666632,
            "__v": 0
        }
    },
    "message": "Authentication successful. AccessToken expires in 10 minutes. RefreshToken expires in 7 days."
}
```

Response (Error 400) example:

```json
{
    "code": "4007",
    "message": "auth.signatureVerificationFailed",
    "details": "BIP322 signature verification failed"
}
```

#### API: Refresh Access Token

Endpoint: `POST /api/auth/refresh-token`\
Purpose: Get new access token when current one expires

Request Body example:

```json
{
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
```

Response (Success 200) example:

```json
{
    "success": true,
    "data": {
        "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
         "wallet": {
            "_id": "68e1fc5a73513ef283c17b2f",
            "deletedAt": null,
            "tradingAddress": "bc1p...",
            "userAddress": "bc1pge...",
            "userPublicKey": "4ae4...",
            "requiredSignNumber": 2,
            "pubKeysNum": 2,
            "agreeTerm": false,
            "createdAt": 1759640666632,
            "updatedAt": 1759640666632,
            "__v": 0
        }
    },
    "message": "New access token generated successfully"
}
```

Response (Error 400) example:

```json
{
    "code": "4005",
    "message": "auth.invalidRefreshToken",
    "details": "Invalid or expired refresh token"
}
```

### cURL Examples

```bash
# Authenticate
curl -X POST http://localhost:8100/api/auth/authenticate \
  -H "Content-Type: application/json" \
  -d '{
    "message": "1759413612750",
    "signature": "AUE5z8iM+Y3M6e...==",
    "address": "bc1py27...zlzu",
    "publicKey": "0324e27cae...7c03"
  }'

# Make protected API call
curl -X POST http://localhost:8100/api/transactions \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." \
  -d '{"userAddress": "bc1p...", "type": "SWAP"}'

# Refresh token
curl -X POST http://localhost:8100/api/auth/refresh-token \
  -H "Content-Type: application/json" \
  -d '{"refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."}'
```

## Testing

### Test Scripts

Use the provided test scripts to verify the authentication system:

```bash
# Test authentication endpoint
node test/auth/scripts/test-authenticate.js

# Test JWT guard with invalid tokens
node test/auth/scripts/test-jwt-guard.js

# Test protected APIs with valid JWT
node test/auth/scripts/test-with-valid-jwt.js

# Test all authentication error codes
node test/auth/scripts/test-auth-errors.js

# Comprehensive test of all protected APIs
node test/auth/scripts/test-protected-apis.js

# Run demo script
./test/auth/scripts/demo-protected-apis.sh
```

### Test Coverage

The test suite covers:

* ✅ Authentication endpoint functionality
* ✅ JWT token validation
* ✅ Protected API access
* ✅ Error code verification
* ✅ Token refresh functionality
* ✅ Multiple authentication scenarios

## Reference Tables

### Endpoint Summary

| Category       | Endpoint                                       | Auth Required | Description            |
| -------------- | ---------------------------------------------- | ------------- | ---------------------- |
| Auth           | `POST /api/auth/authenticate`                  | No            | Initial authentication |
| Auth           | `POST /api/auth/refresh-token`                 | No            | Refresh access token   |
| Wallet         | `POST /api/wallets`                            | No            | Create wallet          |
| Wallet         | `GET /api/wallets`                             | No            | List wallets           |
| Wallet         | `GET /api/wallets/details/:id`                 | No            | Wallet details         |
| Diamond Hand   | `POST /api/diamond-hand/request-claim-rewards` | Yes           | Claim rewards          |
| Transaction    | `POST /api/transactions`                       | Yes           | Create transaction     |
| Transaction    | `POST /api/transactions/sign`                  | Yes           | Sign transaction       |
| VM Transaction | `POST /api/vm-transactions`                    | Yes           | Create VM transaction  |
| Refund         | `POST /api/refunds`                            | Yes           | Create refund          |
| Etch           | `POST /api/etch/submit-etching`                | Yes           | Submit rune etching    |

### Error Code Quick Reference

| Code | Type                          | HTTP Status | Action             |
| ---- | ----------------------------- | ----------- | ------------------ |
| 4002 | Invalid Token                 | 401         | Redirect to login  |
| 4003 | Token Expired                 | 401         | Refresh token      |
| 4004 | Token Not Found               | 401         | Redirect to login  |
| 4005 | Invalid Refresh Token         | 400         | Redirect to login  |
| 4006 | User Not Found                | 401         | Redirect to login  |
| 4007 | Signature Verification Failed | 400         | Show error message |
| 4008 | Wallet Creation Failed        | 400         | Show error message |
| 4009 | Invalid Taproot Address       | 400         | Show error message |

### Token Lifecycle

| Token Type    | Expiration | Purpose            | Storage      |
| ------------- | ---------- | ------------------ | ------------ |
| Access Token  | 10 minutes | API authentication | localStorage |
| Refresh Token | 7 days     | Token renewal      | localStorage |

## Summary

The authentication system provides:

* 8 specific error codes covering all major authentication failure scenarios
* JWT Token Issues: 4002, 4003, 4004, 4005, 4006
* Signature Verification: 4007
* Wallet Operations: 4008, 4009

All error codes are actively used in the codebase and provide clear, actionable feedback for both developers and end users. The system is designed to be secure, stateless, and easy to integrate with frontend applications.


# Affiliate Fee Integration Guide

### Overview

The Affiliate Fee feature allows partners to earn a commission on swap transactions. When a user performs a swap through your platform, a percentage of the input amount is distributed to a designated affiliate recipient address.

This guide explains how to integrate the Affiliate Fee feature into your frontend application, including the calculation formulas and API integration steps.

***

### Table of Contents

1. [Understanding Affiliate Fee](#understanding-affiliate-fee)
2. [Fee Calculation Formula](#fee-calculation-formula)
3. [Amount Calculation with Affiliate Fee](#amount-calculation-with-affiliate-fee)
4. [Transaction Flow](#transaction-flow)
5. [API Integration](#api-integration)
6. [Request/Response Examples](#request-response-examples)
7. [Error Handling](#error-handling)

***

### Understanding Affiliate Fee

#### Key Concepts

* **Affiliate Fee**: A commission paid to a partner (affiliate) for facilitating a swap transaction
* **Basis Points (bps)**: Fee rate expressed in basis points (1% = 10,000 bps, 0.1% = 1,000 bps)
* **Fee Recipient**: The Bitcoin address that receives the affiliate fee
* **Input Token**: The token being swapped FROM (e.g., RUNE or BTC)
* **Output Token**: The token being swapped TO (e.g., BTC or RUNE)

#### How It Works

1. **RUNE → BTC Swap**: Affiliate receives RUNE tokens
   * Fee is deducted from the input RUNE amount
   * Affiliate receives RUNE via Runestone edict
   * Pool receives the remaining RUNE amount
2. **BTC → RUNE Swap**: Affiliate receives BTC
   * Fee is deducted from the input BTC amount
   * Affiliate receives BTC directly
   * Pool receives the remaining BTC amount

#### Important Rules

* Affiliate fee is always calculated on the **input token** amount
* Affiliate fee is deducted **before** the swap calculation
* The pool receives `amountIn - affiliateFee` for the swap
* `amountOut` is calculated based on the pool's received amount (not the original `amountIn`)

***

### Fee Calculation Formula

#### Basic Formula

```javascript
affiliateFeeAmount = floor((amountIn × partnerFeeBps) / 1,000,000)
poolAmountIn = amountIn - affiliateFeeAmount
```

Where:

* `amountIn`: Original input amount (in smallest unit: sats for BTC, base units for RUNE)
* `partnerFeeBps`: Affiliate fee rate in basis points (0-1,000,000)
* `affiliateFeeAmount`: Calculated fee amount (rounded down)
* `poolAmountIn`: Amount that the pool actually receives for the swap

#### Examples

**Example 1: RUNE → BTC Swap**

* `amountIn`: 1,000,000 RUNE
* `partnerFeeBps`: 10,000 (1%)
* Calculation:

  ```javascript
  affiliateFeeAmount = floor((1,000,000 × 10,000) / 1,000,000) = 10,000 RUNE
  poolAmountIn = 1,000,000 - 10,000 = 990,000 RUNE
  ```
* Result: Affiliate receives 10,000 RUNE, pool receives 990,000 RUNE for swap

**Example 2: BTC → RUNE Swap**

* `amountIn`: 1,000,000 sats (0.01 BTC)
* `partnerFeeBps`: 5,000 (0.5%)
* Calculation:

  ```javascript
  affiliateFeeAmount = floor((1,000,000 × 5,000) / 1,000,000) = 5,000 sats
  poolAmountIn = 1,000,000 - 5,000 = 995,000 sats
  ```
* Result: Affiliate receives 5,000 sats, pool receives 995,000 sats for swap

#### Minimum Fee Requirements

* **RUNE → BTC**: Minimum 1 RUNE (if calculated fee < 1, transaction will fail)
* **BTC → RUNE**: Minimum 546 sats (if calculated fee < 546, it will be set to 546 sats automatically)

***

### Amount Calculation with Affiliate Fee

#### Important: Affiliate Fee is Deducted from Input

**Key Point**: The affiliate fee is deducted from `amountIn` **before** the swap calculation. The pool receives the remaining amount after the fee deduction.

#### Calculation Flow

1. **Calculate affiliate fee** from `amountIn`:

   ```javascript
   affiliateFeeAmount = floor((amountIn × partnerFeeBps) / 1,000,000)
   ```
2. **Calculate pool input** (amount that actually goes into the pool):

   ```javascript
   poolAmountIn = amountIn - affiliateFeeAmount
   ```
3. **Calculate `amountOut`** based on `poolAmountIn` (not the original `amountIn`):

   ```javascript
   amountOut = calculateSwapOutput(poolAmountIn)
   ```

#### Example

**Scenario**: User swaps 100 sats BTC with 1% affiliate fee (10,000 bps)

```javascript
amountIn = 100 sats
affiliateFeeAmount = floor((100 × 10,000) / 1,000,000) = 1 sat
poolAmountIn = 100 - 1 = 99 sats
amountOut = calculateSwapOutput(99 sats)  // Calculate based on 99 sats, not 100!
```

**Result**:

* Affiliate receives: 1 sat
* Pool receives: 99 sats for swap
* User receives: `amountOut` calculated from 99 sats

***

### Transaction Flow

The complete transaction flow consists of two steps:

1. **Create Transaction** (`POST /transactions`): Get the unsigned PSBT
2. **Sign and Broadcast** (`POST /transactions/sign`): Sign the PSBT and broadcast to network

#### Step 1: Create Transaction

The first step creates an unsigned Partially Signed Bitcoin Transaction (PSBT) that the user needs to sign.

#### Step 2: Sign and Broadcast

After receiving the PSBT, the frontend must:

1. Sign the PSBT using the user's private key for inputs specified in `userInputIndexes`
2. Send the signed transaction back to the backend via `/transactions/sign` endpoint
3. Backend validates and broadcasts the transaction to the Bitcoin network

***

### API Integration

#### Endpoint 1: Create Transaction

```
POST /transactions
```

#### Authentication

All requests require JWT authentication via `Authorization: Bearer <token>` header.

#### Request Body

```json
{
  type: "SWAP",
  params: {
    tokens: ["<tokenInId>", "<tokenOutId>"],  // e.g., ["2584333:39", "0:0"]
    amountIn: "<amount>",                      // String, in smallest unit
    amountOut: "<amount>",                     // String, in smallest unit
    feeRates: [<feeRate>],                     // Array of numbers (e.g., [3000])
    isExactIn: true,                           // boolean
    partnerFeeBps: <number>,                  // Optional: 0-1,000,000
    partnerFeeRecipient: "<address>",          // Optional: Bitcoin address
    userAddress: "<address>"                  // Optional: Will use JWT user if omitted
  }
}
```

#### Parameters

| Parameter             | Type       | Required | Description                                                                                |
| --------------------- | ---------- | -------- | ------------------------------------------------------------------------------------------ |
| `tokens`              | `string[]` | Yes      | Array of 2 token IDs: `[tokenInId, tokenOutId]`                                            |
| `amountIn`            | `string`   | Yes      | Input amount in smallest unit (sats for BTC, base units for RUNE)                          |
| `amountOut`           | `string`   | Yes      | Expected output amount in smallest unit                                                    |
| `feeRates`            | `number[]` | Yes      | Pool fee rates (e.g., `[3000]` for 0.3%)                                                   |
| `isExactIn`           | `boolean`  | Optional | `true` for exact input, `false` for exact output (default: `true`)                         |
| `partnerFeeBps`       | `number`   | Optional | Affiliate fee in basis points (0-1,000,000). Required if `partnerFeeRecipient` is provided |
| `partnerFeeRecipient` | `string`   | Optional | Bitcoin address to receive affiliate fee. Required if `partnerFeeBps` is provided          |
| `userAddress`         | `string`   | Optional | User's Bitcoin address (from JWT if omitted)                                               |

#### Validation Rules

1. If `partnerFeeBps` is provided, `partnerFeeRecipient` is **required**
2. If `partnerFeeRecipient` is provided, `partnerFeeBps` is **required**
3. `partnerFeeRecipient` cannot be the same as:
   * Pool address
   * User's trading address
4. For RUNE → BTC: Minimum affiliate fee is 1 RUNE
5. For BTC → RUNE: Minimum affiliate fee is 546 sats (auto-adjusted if lower)

#### Response

```json
{
  code: "1",  // String: "1" for success
  message: "common.success",
  data: {
    base64Psbt: "<base64_encoded_psbt>",  // Partially Signed Bitcoin Transaction
    fee: {
      feeRate: <number>,    // Fee rate in sat/vB
      totalFee: <number>    // Total fee in sats
    },
    userInputIndexes: [<number>],  // Array of input indices that user needs to sign
    txId: "<transaction_id>"        // Transaction ID (available after signing)
  }
}
```

**Note**: The response does not include affiliate fee information. The affiliate fee is automatically deducted and distributed during transaction execution. You can verify the affiliate fee by checking the transaction outputs after the transaction is confirmed.

#### Endpoint 2: Sign and Broadcast Transaction

```
POST /transactions/sign
```

After receiving the `base64Psbt` from the create transaction endpoint, you need to:

1. **Sign the PSBT** on the frontend using the user's private key
   * Sign only the inputs specified in `userInputIndexes` array
   * Use Bitcoin signing libraries (e.g., `@scure/btc-signer`, `bitcoinjs-lib`)
2. **Send signed transaction** to this endpoint

**Request Body**

```json
{
  type: "SWAP",
  params: {
    signedBase64Tx: "<base64_encoded_signed_transaction>",
    userAddress: "<address>"  // Optional: Will use JWT user if omitted
  }
}
```

**Parameters**

| Parameter               | Type     | Required | Description                                                 |
| ----------------------- | -------- | -------- | ----------------------------------------------------------- |
| `type`                  | `string` | Yes      | Transaction type (e.g., `"SWAP"`)                           |
| `params.signedBase64Tx` | `string` | Yes      | Base64 encoded signed transaction (PSBT after user signing) |
| `params.userAddress`    | `string` | Optional | User's Bitcoin address (from JWT if omitted)                |

**Response**

```json
{
  code: "1",
  message: "common.success",
  data: {
    txId: "<transaction_id>"  // Final transaction ID after broadcast
  }
}
```

**Example Request**

```json
{
  "type": "SWAP",
  "params": {
    "signedBase64Tx": "<base64_encoded_signed_transaction_example>"
  }
}
```

**Example Response**

```json
{
  "code": "1",
  "message": "common.success",
  "data": {
    "txId": "<transaction_id_example>"
  }
}
```

**Note**: After successful signing and broadcast, the transaction is submitted to the Bitcoin network. The `txId` in the response is the final transaction ID that can be used to track the transaction on blockchain explorers.

***

### Request/Response Examples

**Note**: All addresses, transaction IDs, and PSBT data in the examples below are placeholder values for demonstration purposes only. Replace them with actual values when making real API calls.

#### Example 1: RUNE → BTC Swap with 1% Affiliate Fee

**Request:**

```json
{
  "type": "SWAP",
  "params": {
    "tokens": ["2584333:39", "0:0"],
    "amountIn": "1000000",
    "amountOut": "100000",
    "feeRates": [3000],
    "isExactIn": true,
    "partnerFeeBps": 10000,
    "partnerFeeRecipient": "bc1qexample1234567890abcdefghijklmnopqrstuvwxyz"
  }
}
```

**Calculation:**

* `amountIn`: 1,000,000 RUNE
* `affiliateFeeAmount`: floor((1,000,000 × 10,000) / 1,000,000) = 10,000 RUNE
* `poolAmountIn`: 1,000,000 - 10,000 = 990,000 RUNE
* Pool performs swap with 990,000 RUNE → calculates `amountOut`

**Response:**

```json
{
  "code": "1",
  "message": "common.success",
  "data": {
    "base64Psbt": "<base64_encoded_psbt_example>",
    "fee": {
      "feeRate": 3,
      "totalFee": 4926
    },
    "userInputIndexes": [9, 10, 11],
    "txId": "<transaction_id_example>"
  }
}
```

**Note**: The `txId` is available immediately after the transaction is created. The `base64Psbt` contains the partially signed transaction that needs to be signed by the user using the indices in `userInputIndexes`.

#### Example 2: BTC → RUNE Swap with 0.5% Affiliate Fee

**Request:**

```json
{
  "type": "SWAP",
  "params": {
    "tokens": ["0:0", "2584333:39"],
    "amountIn": "1000000",
    "amountOut": "990000",
    "feeRates": [3000],
    "isExactIn": true,
    "partnerFeeBps": 5000,
    "partnerFeeRecipient": "bc1qexample1234567890abcdefghijklmnopqrstuvwxyz"
  }
}
```

**Calculation:**

* `amountIn`: 1,000,000 sats (0.01 BTC)
* `affiliateFeeAmount`: floor((1,000,000 × 5,000) / 1,000,000) = 5,000 sats
* `poolAmountIn`: 1,000,000 - 5,000 = 995,000 sats
* Pool performs swap with 995,000 sats → calculates `amountOut`

**Response:**

```json
{
  "code": "1",
  "message": "common.success",
  "data": {
    "base64Psbt": "<base64_encoded_psbt_example>",
    "fee": {
      "feeRate": 3,
      "totalFee": 4926
    },
    "userInputIndexes": [9, 10, 11],
    "txId": "<transaction_id_example>"
  }
}
```

#### Example 3: Swap without Affiliate Fee

**Request:**

```json
{
  "type": "SWAP",
  "params": {
    "tokens": ["2584333:39", "0:0"],
    "amountIn": "1000000",
    "amountOut": "100000",
    "feeRates": [3000],
    "isExactIn": true
  }
}
```

**Note:** Simply omit `partnerFeeBps` and `partnerFeeRecipient` fields.

***

### Error Handling

#### Common Errors

**1. Missing Recipient**

```json
{
  "code": "1006",
  "message": "common.invalidRequest",
  "name": "BadRequestException",
  "error": {
    "message": "common.invalidRequest",
    "details": "partnerFeeRecipient is required when partnerFeeBps is provided",
    "method": "POST",
    "path": "/api/transactions",
    "timestamp": "2025-11-26T09:16:47.918Z"
  }
}
```

**Solution:** Always provide both `partnerFeeBps` and `partnerFeeRecipient` together.

**2. Invalid Recipient Address**

```json
{
  "code": "1006",
  "message": "common.invalidRequest",
  "name": "BadRequestException",
  "error": {
    "message": "common.invalidRequest",
    "details": "partnerFeeRecipient cannot be the same as pool address",
    "method": "POST",
    "path": "/api/transactions",
    "timestamp": "2025-11-26T09:16:47.918Z"
  }
}
```

**Solution:** Ensure affiliate recipient is different from pool and user addresses.

**3. Fee Too Small (RUNE → BTC)**

```json
{
  "code": "1006",
  "message": "common.invalidRequest",
  "name": "BadRequestException",
  "error": {
    "message": "common.invalidRequest",
    "details": "Affiliate fee must be at least 1 Rune. Calculated fee: 0 Rune",
    "method": "POST",
    "path": "/api/transactions",
    "timestamp": "2025-11-26T09:16:47.918Z"
  }
}
```

**Solution:** Increase `partnerFeeBps` or `amountIn` to ensure minimum 1 RUNE fee.

**4. Fee Too Large**

```json
{
  "code": "1006",
  "message": "common.invalidRequest",
  "name": "BadRequestException",
  "error": {
    "message": "common.invalidRequest",
    "details": "Affiliate fee is too large. Pool amount would be 0",
    "method": "POST",
    "path": "/api/transactions",
    "timestamp": "2025-11-26T09:16:47.918Z"
  }
}
```

**Solution:** Reduce `partnerFeeBps` or increase `amountIn`.

**5. Insufficient Balance**

```json
{
  "code": "<error_code>",
  "message": "wallet.insufficientBalance",
  "name": "HttpException",
  "error": {
    "message": "wallet.insufficientBalance",
    "details": "Insufficient balance",
    "method": "POST",
    "path": "/api/transactions",
    "timestamp": "2025-11-26T09:16:47.918Z"
  }
}
```

**Solution:** Check user's balance before initiating swap.

#### Error Response Format

All error responses follow this structure:

* `code`: Error code (string, e.g., `"1006"`). Success is `"1"`, any other value indicates an error
* `message`: Error message key (e.g., `"common.invalidRequest"`)
* `name`: Exception type (e.g., `"BadRequestException"`, `"HttpException"`)
* `error`: Object containing detailed error information
  * `message`: Error message key
  * `details`: Human-readable error description
  * `method`: HTTP method used
  * `path`: API endpoint path
  * `timestamp`: Error timestamp

***

### Summary

#### Key Takeaways

1. **Affiliate fee is deducted from input amount** before swap calculation
2. **Formula**: `affiliateFeeAmount = floor((amountIn × partnerFeeBps) / 1,000,000)`
3. **Pool receives**: `amountIn - affiliateFeeAmount`
4. **Always provide both** `partnerFeeBps` and `partnerFeeRecipient` together
5. **Minimum fees**: 1 RUNE for RUNE swaps, 546 sats for BTC swaps
6. **Use backend quote API** for accurate amount calculations when possible

#### Quick Reference

**Formula:**

```javascript
affiliateFeeAmount = floor((amountIn × partnerFeeBps) / 1,000,000)
poolAmountIn = amountIn - affiliateFeeAmount
```

**API Request Structure:**

```json
{
  "type": "SWAP",
  "params": {
    "tokens": ["<tokenInId>", "<tokenOutId>"],
    "amountIn": "<amount>",
    "amountOut": "<amount>",
    "feeRates": [<feeRate>],
    "partnerFeeBps": <number>,        // 1% = 10,000 bps
    "partnerFeeRecipient": "<address>" // Required if partnerFeeBps provided
  }
}
```

***

### Support

For questions or issues with affiliate fee integration, please contact the development team or refer to the main API documentation.


# Terms and Conditions

These Terms and Conditions ("Terms") govern your access to and use of the radFi Platform, including any services, features, or content provided through radFi (collectively, the "Services"). By accessing or using the Services, you agree to be bound by these Terms. If you do not agree to these Terms, you may not use the Services.

### 1. Acceptance of Terms

radFi is an automated market maker facilitated by Lydia Labs SRL ("we," "us," or "our"). These Terms constitute a legally binding agreement between you ("User," "you," or "your") and us. We reserve the right to update or modify these Terms at any time, with notice provided via the Platform or other reasonable means. Your continued use of the Services after such changes constitutes acceptance of the updated Terms.

### 2. Nature of the Platform

radFi is an Automated Market Maker (AMM) protocol for Bitcoin and bitcoin tokens (e.g. Runes) that executes trades entirely on Bitcoin main net and leverages smart contracts on the Sonic Network to calculate and propagate trade quotes and liquidity. This enables two primary activities for Bitcoin users:

1. **Automated Market Making** - Users interested in earning yield on their BTC and runes by trading within a customizable range can deposit their assets into radFi “Agency Pools” (further defined in the “Agency Pool mechanics” section) to execute their chosen strategy. These users are known as Liquidity Providers (“LPs”).
2. **Instant Swaps** - Users interested in buying or selling runes can execute trades with near instant execution multiple times within a single block, with settlement happening at the end of a block. These users are known as “traders”.

**Trading Wallet**

* Prior to using radFi, users must request and fund a trading wallet. A trading wallet is a 2/2 multi-signature wallet between the user and radFi, in which radFi’s signature has a predetermined expiration date allowing users to maintain custody of assets held in their trading wallet.&#x20;

**Agency Pool mechanics**

* Users transfer BTC and/or bitcoin tokens (e.g. runes) from their trading wallet to radFi. The transfer includes instructions with a desired fee tier and price range to trade between BTC and the selected token.&#x20;
* Pricing is determined using the constant product formula within the desired range. The constant product formula is expressed as x \* y = k

**Fee Earnings**

* Liquidity Providers earn a portion of the trading fees generated by the agency pool, proportional to their share. Fee tiers may vary by pool, and are set by the liquidity provider. Fees accrue automatically and are claimable at any time.
* radFi takes a platform fee (specified in the official radFi documentation) as claimable by users that add liquidity to an Agency Pool
* radFi reserves the right to alter the percentage of Fee earnings taken as a platform fee

**Risks You Accept**

* Impermanent Loss (IL): If the price of the pooled tokens diverges significantly from when you deposited, you may experience IL. This is a potential loss in value compared to simply holding the tokens outside the pool, though it’s "impermanent" until you withdraw.
* Software Risk: You rely on radFi’s software being secure. There’s no guarantee against bugs or exploits that could lead to loss of funds.
* Volatility and Slippage: Your returns depend on trading volume and price stability within your chosen range. Outside that range, your funds earn no fees and may lose value relative to market prices.
* No Custodial Oversight: If you lose access to your wallet or send funds incorrectly, there’s no support to recover them.

**Immutability and Governance**

* You accept that future governance decisions could impact pool economics, though core mechanics remain fixed.

**Withdrawal and Exit**

* You can remove liquidity from an agency pool at any time by signing a transaction on the Bitcoin blockchain containing the details of the request, which will then be validated by radFi and the Sonic smart contracts. You will then receive your share of the pool’s assets plus accrued fees. However, the value and composition of what you withdraw depend on the pool’s state at that moment.

**No Guarantees or Legal Recourse**

* radFi offers no warranties or customer support. The protocol’s documentation explicitly states it’s experimental software provided "as is." If something goes wrong (e.g., a hack or loss due to IL), there’s no entity to sue or claim compensation from—your recourse is limited to the blockchain’s transparency and community response.

**Network Fees**

* Interacting with radFi (depositing, withdrawing, claiming fees) requires paying Bitcoin network fees, which can be significant during congestion. These are not radFi’s fees but a cost of using Bitcoin, and you’re responsible for them.

### 3. Eligibility

The Services are not available to U.S. citizens or residents. To use the Services, you must:

* Be at least 18 years of age or the age of legal majority in your jurisdiction;
* Not be a citizen or resident of the United States of America, including its territories and possessions;
* Not be located in, or a resident of, any country or region subject to U.S. sanctions or embargoes, or otherwise prohibited from accessing the Services under applicable law;
* Not be a person or entity barred from using the Services under applicable regulations in your jurisdiction.

By accessing or using the Services, you represent and warrant that you are not a U.S. citizen or resident and that you meet all other eligibility requirements. We reserve the right to restrict access to the Platform from IP addresses or locations associated with the United States.

### 4. User Responsibilities

#### 4.1 Wallet Security

You are responsible for securing your blockchain wallet, private keys, and any credentials used to access the Services. We are not liable for any loss or damage resulting from unauthorized access to your wallet or loss of your private keys.

#### 4.2 Compliance with Laws

You agree to use the Services in compliance with all applicable laws in your jurisdiction, including but not limited to securities laws, tax obligations, and anti-money laundering (AML) regulations where applicable. You acknowledge that the Services are not intended for use by U.S. citizens or residents and agree not to access the Services from within the United States.

#### 4.3 Prohibited Activities

You may not use the Services to:

* Engage in illegal activities, including money laundering, terrorist financing, or fraud;
* Trade assets that infringe intellectual property rights or are otherwise unlawful;
* Manipulate markets or engage in deceptive trading practices;
* Attempt to interfere with, hack, or disrupt the Platform or its underlying blockchain network;
* Circumvent restrictions on U.S. citizens or residents, including through the use of VPNs or other anonymizing tools.

### 5. Risks

You acknowledge and accept the following risks associated with using the Services:

* Volatility and Slippage: Your returns depend on trading volume and price stability within your chosen range. Outside that range, your funds earn no fees and may lose value relative to market prices.
* Impermanent Loss (IL): If the price of the pooled tokens diverges significantly from when you deposited, you may experience IL. This is a potential loss in value compared to simply holding the tokens outside the pool, though it’s "impermanent" until you withdraw.
* Immutability and Governance: You accept that future governance decisions could impact pool economics, though core mechanics remain fixed.
* Exploitation Risk: Transactions rely on software, which may contain bugs or vulnerabilities beyond our control.
* Regulatory Uncertainty: Changes in laws or regulations in your jurisdiction or the United States may impact your ability to use the Services or the legality of certain assets.
* Loss of Funds: Errors, hacks, or loss of private keys may result in irretrievable loss of assets.

### 6. Fees

The Platform may charge fees for certain transactions or services (e.g., gas fees, liquidity pool fees, or trading fees), which will be disclosed at the time of the transaction. You are responsible for all blockchain network fees associated with your use of the Services.&#x20;

### 7. No Warranties

The Services are provided on an "as-is" and "as-available" basis. We make no warranties, express or implied, regarding the availability, accuracy, or reliability of the Platform or Services. We disclaim any liability for interruptions, errors, or losses resulting from the use of the Services.

### 8. Limitation of Liability

To the fullest extent permitted by law, Lydia Labs SRL and its affiliates, officers, directors, employees, or agents shall not be liable for any direct, indirect, incidental, consequential, or punitive damages arising from your use of the Services, including but not limited to loss of funds, data, or profits. Our total liability, if any, shall not exceed $100 USD.

### 9. Indemnification

You agree to indemnify and hold harmless Lydia Labs SRL and its affiliates from any claims, losses, damages, or expenses (including legal fees) arising from your use of the Services, violation of these Terms (including accessing the Services as a U.S. citizen or resident), or infringement of any third-party rights.

### 10. Intellectual Property

All content, trademarks, and intellectual property related to the Platform are owned by Lydia Labs SRL or its licensors. You are granted a limited, non-exclusive, non-transferable license to use the Services for personal, non-commercial purposes, subject to these Terms.

### 11. Termination

We reserve the right to suspend or terminate your access to the Services at our sole discretion, with or without notice, for any reason, including suspected violation of these Terms (e.g., use by U.S. citizens or residents) or applicable law.

### 12. Governing Law and Dispute Resolution

You waive any right to participate in a class action lawsuit or class-wide arbitration. Any disputes arising from these Terms shall be resolved through binding arbitration in a jurisdiction of our discretion.

### 13. Privacy

Our collection and use of your personal information, if any, are governed by our Privacy Policy, available at [**Privacy Policy**](/privacy-policy). As a platform, we may not collect identifiable personal data unless required by law or specific services. You acknowledge that accessing the Services from the United States may result in termination of access and reporting to relevant authorities if required.

### 14. Force Majeure

We are not liable for delays or failures in performance due to events beyond our reasonable control, including but not limited to natural disasters, cyberattacks, or blockchain network failures.

### 15. Contact Us

For questions or support, raise a ticket in the Discord server, which can be accessed with this [**link**](https://discord.gg/5y8hE4Xtn8).

\
\ <br>

<br>


# Privacy Policy

### **1. Introduction**

This Privacy Policy explains how radFi, a platform for automated trading of digital assets on Bitcoin, handles data. Like other AMM protocols, we operate via software and aim to facilitate peer-to-pool trading without intermediaries. We prioritize transparency while acknowledging the public nature of blockchain interactions.

### **2. Information We Collect**

Our data collection is minimal and shaped by our structure:

* **Blockchain Data**: When you connect a wallet to our platform or interface, we collect your publicly available blockchain address and transaction details (e.g., token swaps, amounts). This data is logged on-chain and inherently visible to anyone via blockchain explorers.&#x20;
* **Usage Data**: If you use our front-end interface (e.g., a website), we may collect anonymized off-chain data such as device type, browser version, or interaction logs (e.g., clicks on the interface).&#x20;
* **Voluntary Data**: If you contact us (e.g., via support channels), any info you provide (e.g., email) is processed only for that purpose.

### **3. How We Use Your Information**

* **Transaction Processing**: Blockchain data enables trade execution and liquidity provision, core to our AMM functionality.
* **Improvement**: Usage data helps us optimize the interface.
* **Security**: We may screen wallet addresses against illicit activity, to prevent harm or comply with legal standards.

### **4. Data Sharing and Disclosure**

* **Public Blockchain**: All on-chain activity (e.g., trades, liquidity provision) is public and immutable, beyond our control.
* **Third Parties**: We may share wallet addresses with service providers for technical support or risk assessment.&#x20;
* **Legal Obligations**: If compelled by law, we may disclose data, though our platform setup limits our ability to do so.

### **5. Data Security**

We secure any off-chain data we control (e.g., interface logs) with industry-standard measures like encryption. However, we’re not responsible for blockchain security or your wallet’s safety—users must protect their private keys.

### **6. User Rights and Choices**

* **Transparency**: Check your on-chain activity via blockchain explorers.
* **Opt-Out**: You can disable interface cookies, if applicable, though core AMM use relies on blockchain interaction.
* **No Accounts**: We don’t maintain user accounts, so there’s no personal data to delete—your wallet is your control point.

### **7. Changes to the Policy**

We may update this policy, posting changes on our site or governance channels.

### **8. Contact Information**

Reach us at <hi@lydialabs.xyz> for questions.


