# 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.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.radfi.co/affiliate-fee-integration-guide.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
