# API Docs v2

Welcome to the **Powered by Obsidian** integration guide.

This documentation is for third-party projects that want to:

* Route swaps through Obsidian's aggregator
* Display Obsidian's whitelisted token list and logos
* Earn quest tracking attribution for their users when routing through approved middleman contracts

{% hint style="info" %}
**Note.** This is a public partner guide. It only covers supported external integration flows and onboarding requirements.
{% endhint %}

{% hint style="info" %}
**AI-friendly by design.** The endpoint summaries, request bodies, and examples in this guide are structured so they can be handed directly to your editor or coding assistant with minimal rewriting. See Section 5 for prompts and workflows.
{% endhint %}

## Quick Reference

### Environment

| Item                     | Value                                        |
| ------------------------ | -------------------------------------------- |
| Base URL                 | `https://api.obsidian.finance`               |
| Cronos chain ID          | `25`                                         |
| Native token placeholder | `0x0000000000000000000000000000000000000000` |
| WCRO                     | `0x5C7F8A570d578ED84E63fdFA7b1eE72dEae1AE23` |
| Obsidian router          | `0x1189331089b6ca8beA989C1F2fFd0EfAdCd33a69` |

### Capabilities

| Capability                                      | Status               | Notes                                       |
| ----------------------------------------------- | -------------------- | ------------------------------------------- |
| Direct swap integration                         | ✅ Supported          | Public quote and calldata routes            |
| Public token list and logos                     | ✅ Supported          | Helper route for token pickers and branding |
| Quest tracking for approved middleman contracts | 🔒 Supported (gated) | Requires review and a partner API key       |
| Custom dual-recipient partner fee splitting     | ⛔ Not in public flow | Not documented here                         |

### Endpoint Summary

| Purpose                                     | Method | Route                             |
| ------------------------------------------- | ------ | --------------------------------- |
| Get quote                                   | `POST` | `/:chainId/v1/getquote`           |
| Build swap calldata                         | `POST` | `/v1/getswap`                     |
| Get public token list                       | `GET`  | `/:chainId/v1/whitelisted-tokens` |
| Submit partner tx hashes for quest tracking | `POST` | `/v1/partners/trades/ingest`      |

## 1. Direct Swap Integration

The end-to-end partner swap flow is three steps:

1. Call `POST /:chainId/v1/getquote`
2. Call `POST /v1/getswap`
3. Broadcast the returned router transaction

The currently documented execution path is `EXACT_INPUT`.

### 1.0 Workflow Overview

```
Your App      ──▶ POST /:chainId/v1/getquote       ──▶ Obsidian Quote API
              ◀── quote, routes, gas estimate      ◀──

Your App      ──▶ POST /v1/getswap                 ──▶ Obsidian Calldata API
              ◀── calldata, routerAddress, value   ◀──

Your User     ──▶ signs and broadcasts transaction ──▶ Cronos

Your Backend  ──▶ POST /v1/partners/trades/ingest  ──▶ Obsidian Quest Tracking
              ◀── processed / skipped / failed     ◀──
```

### 1.1 Get Quote

**Route**

```http
POST /25/v1/getquote
```

**Request body**

```json
{
  "currency": {
    "address": "0x7A86f7f7cF2F08E8D4B7C13C7C6F54C5B8d38fA8",
    "decimals": 18,
    "symbol": "TOKEN",
    "name": "Example Token"
  },
  "tradeType": 0,
  "amount": {
    "currency": {
      "address": "0x0000000000000000000000000000000000000000",
      "decimals": 18,
      "symbol": "CRO",
      "name": "Cronos"
    },
    "value": "150000000000000000000"
  },
  "maxSplits": 0
}
```

**Request notes**

* Use `tradeType: 0` for exact-input quoting.
* Use the zero address `0x0000...0000` for native CRO.
* `amount.value` must be the raw integer amount in smallest units.
* `maxSplits: 0` is the simplest default.

**Response highlights**

| Field          | Meaning                            |
| -------------- | ---------------------------------- |
| `fee`          | Quote fee metadata                 |
| `gasEstimate`  | Estimated gas for the quoted route |
| `inputAmount`  | Effective input amount             |
| `outputAmount` | Effective output amount            |
| `routes`       | Route breakdown used for execution |
| `tradeType`    | Returned trade type metadata       |

{% hint style="info" %}
**Implementation notes.** The quote fee currently resolves to `0.004` (`0.4%`). `gasEstimateInUSD` exists in the response shape, but should not be treated as authoritative today.
{% endhint %}

### 1.2 Build Swap Calldata

**Route**

```http
POST /v1/getswap
```

Converts a quote into executable router calldata.

**Request body**

```json
{
  "trade": {
    "tradeType": "EXACT_INPUT",
    "inputAmount": {
      "currency": {
        "isNative": true,
        "wrapped": {
          "address": "0x0000000000000000000000000000000000000000"
        }
      },
      "quotient": "150000000000000000000"
    },
    "outputAmount": {
      "currency": {
        "isNative": false,
        "wrapped": {
          "address": "0x7A86f7f7cF2F08E8D4B7C13C7C6F54C5B8d38fA8"
        }
      },
      "quotient": "123456789000000000000"
    },
    "routes": [
      {
        "path": [
          {
            "wrapped": {
              "address": "0x5C7F8A570d578ED84E63fdFA7b1eE72dEae1AE23"
            }
          },
          {
            "wrapped": {
              "address": "0x7A86f7f7cF2F08E8D4B7C13C7C6F54C5B8d38fA8"
            }
          }
        ],
        "pools": [
          {
            "address": "0xYourPoolAddress"
          }
        ],
        "inputAmount": {
          "currency": {
            "isNative": true,
            "wrapped": {
              "address": "0x0000000000000000000000000000000000000000"
            }
          },
          "quotient": "150000000000000000000"
        },
        "outputAmount": {
          "currency": {
            "isNative": false,
            "wrapped": {
              "address": "0x7A86f7f7cF2F08E8D4B7C13C7C6F54C5B8d38fA8"
            }
          },
          "quotient": "123456789000000000000"
        },
        "type": "V2"
      }
    ]
  },
  "options": {
    "slippageTolerance": {
      "numerator": "5",
      "denominator": "1000"
    },
    "recipient": "0xYourUserWallet",
    "deadlineOrPreviousBlockhash": "1735689600"
  },
  "chainId": 25,
  "userAddress": "0xYourUserWallet"
}
```

**Mapping rules: quote → trade**

| From `quote`                       | To `trade`                                |
| ---------------------------------- | ----------------------------------------- |
| `quote.inputAmount.value`          | `trade.inputAmount.quotient`              |
| `quote.outputAmount.value`         | `trade.outputAmount.quotient`             |
| `quote.routes[i].path[j].address`  | `trade.routes[i].path[j].wrapped.address` |
| `quote.routes[i].pools[k].address` | `trade.routes[i].pools[k].address`        |
| (constant)                         | `trade.tradeType = "EXACT_INPUT"`         |

Additional rules:

* When a token is native CRO, always set `isNative: true`.
* Preserving the native zero address at the trade-level input is acceptable.
* Route paths still use wrapped-token hop addresses, so native CRO routes usually begin with WCRO.
* For native output, use the WCRO address anywhere a wrapped output address is required. The API appends the unwrap step internally.

**Response highlights**

| Field           | Meaning                                        |
| --------------- | ---------------------------------------------- |
| `calldata`      | Router calldata to broadcast                   |
| `value`         | Native value to send with the tx               |
| `routerAddress` | Router contract to call                        |
| `estimatedGas`  | Estimated gas usage                            |
| `paths`         | Path and pool breakdown                        |
| `recipient`     | Final swap recipient                           |
| `multiHopRoute` | Whether the route spans multiple hops          |
| `feeApplied`    | Obsidian fee metadata in the built transaction |

**Broadcasting**

Send the user's transaction with:

| Tx field | Source          |
| -------- | --------------- |
| `to`     | `routerAddress` |
| `data`   | `calldata`      |
| `value`  | `value`         |

{% hint style="info" %}
**Execution guidance.** `estimatedGas` already includes an API-side safety buffer. If you add more padding client-side, keep it conservative. `deadlineOrPreviousBlockhash` should usually be a future Unix timestamp. If you omit it, Obsidian defaults the deadline to roughly **10 minutes** from request time.
{% endhint %}

### 1.3 Compact End-to-End Example

The fastest possible implementation handoff to an engineer or an LLM.

{% stepper %}
{% step %}

### Get a quote

```bash
curl -s https://api.obsidian.finance/25/v1/getquote \
  -H 'Content-Type: application/json' \
  -d '{
    "currency": {
      "address": "0x6135c840c511a112457d647Aad82a26412a4fa7D",
      "decimals": 18,
      "symbol": "LAZY",
      "name": "Lazy Horse"
    },
    "tradeType": 0,
    "amount": {
      "currency": {
        "address": "0x0000000000000000000000000000000000000000",
        "decimals": 18,
        "symbol": "CRO",
        "name": "Cronos"
      },
      "value": "150000000000000000000"
    },
    "maxSplits": 0
  }'
```

{% endstep %}

{% step %}

### Build swap calldata from the returned quote

```bash
curl -s https://api.obsidian.finance/v1/getswap \
  -H 'Content-Type: application/json' \
  -d '{
    "trade": {
      "tradeType": "EXACT_INPUT",
      "inputAmount": {
        "currency": {
          "wrapped": { "address": "0x0000000000000000000000000000000000000000" },
          "isNative": true
        },
        "quotient": "150000000000000000000"
      },
      "outputAmount": {
        "currency": {
          "wrapped": { "address": "0x6135c840c511a112457d647Aad82a26412a4fa7D" },
          "isNative": false
        },
        "quotient": "20028343121762563187544"
      },
      "routes": [
        {
          "path": [
            { "wrapped": { "address": "0x5C7F8A570d578ED84E63fdFA7b1eE72dEae1AE23" } },
            { "wrapped": { "address": "0x3b41B27E74Dd366CE27cB389dc7877D4e1516d4d" } },
            { "wrapped": { "address": "0x6135c840c511a112457d647Aad82a26412a4fa7D" } }
          ],
          "pools": [
            { "address": "0xA51231984ff01F4933a9FA24E8fd143f18ae6772" },
            { "address": "0xD9A74F078418e66BF787036c2a722B3c38704281" }
          ],
          "inputAmount": {
            "currency": {
              "wrapped": { "address": "0x0000000000000000000000000000000000000000" },
              "isNative": true
            },
            "quotient": "150000000000000000000"
          },
          "outputAmount": {
            "currency": {
              "wrapped": { "address": "0x6135c840c511a112457d647Aad82a26412a4fa7D" },
              "isNative": false
            },
            "quotient": "20028343121762563187544"
          },
          "type": "V2"
        }
      ]
    },
    "options": {
      "slippageTolerance": {
        "numerator": "5",
        "denominator": "1000"
      },
      "recipient": "0xYourUserWallet",
      "deadlineOrPreviousBlockhash": "1747804299"
    },
    "chainId": 25,
    "userAddress": "0xYourUserWallet"
  }'
```

{% endstep %}

{% step %}

### Send the transaction

Use the `/getswap` response as:

| Tx field   | Source          |
| ---------- | --------------- |
| `to`       | `routerAddress` |
| `data`     | `calldata`      |
| `value`    | `value`         |
| `gasLimit` | `estimatedGas`  |

Your app or wallet client then signs and broadcasts the transaction normally.
{% endstep %}
{% endstepper %}

## 2. Public Token List

Pull token metadata and logos for swap forms, token pickers, and branding.

**Route**

```http
GET /:chainId/v1/whitelisted-tokens
```

**Example**

```http
GET /25/v1/whitelisted-tokens
```

**Returned token fields**

| Field      | Meaning          |
| ---------- | ---------------- |
| `name`     | Token name       |
| `symbol`   | Token symbol     |
| `address`  | Contract address |
| `decimals` | Token decimals   |
| `logoURI`  | Token logo URL   |

**Rate limits**

* Public route
* Rate limited per IP
* Current default limit: **60 requests per minute**

## 3. Quest Tracking for Partner Contracts

If your project uses its own middleman contract and still wants Obsidian quest tracking, onboarding starts with a Discord ticket.

### 3.1 Open a Discord Ticket With

| Required item                              | Notes                                                |
| ------------------------------------------ | ---------------------------------------------------- |
| Project name and primary contact           | Main point of contact for follow-up                  |
| Target chain ID                            | For example `25` for Cronos                          |
| Deployed contract address or addresses     | Include all relevant live contracts                  |
| Block explorer links                       | Explorer links for each contract                     |
| Verified contract on the relevant explorer | Obsidian can pull contract details from there        |
| Flow description                           | Explain how your contract routes swaps into Obsidian |
| One to three real successful tx hashes     | Use live successful examples                         |
| Expected end-user wallet for each tx hash  | Needed for attribution validation                    |
| Fee-flow explanation                       | Include if your contract skims fees before routing   |

### 3.2 Contract Requirements

The important requirement is not the outer contract brand or implementation style. The important requirement is that **Obsidian can prove the end user and the actual routed swap.**

Your contract must:

* Forward a decodable Obsidian router call inside the transaction flow
* Route the swap so the nested Obsidian recipient is the real end user
* Provide a reliable way to attribute the end user

**Supported attribution sources**

* Transaction sender
* A function argument
* An emitted event argument

**Preferred event fields**

| Field          | Priority |
| -------------- | -------- |
| `user`         | Required |
| `inputToken`   | Required |
| `inputAmount`  | Required |
| `feeAmount`    | Required |
| `outputToken`  | Helpful  |
| `outputAmount` | Helpful  |

**Example event shape**

```solidity
event Swap(
  address indexed user,
  address inputToken,
  uint256 inputAmount,
  uint256 feeAmount,
  address outputToken,
  uint256 outputAmount
);
```

{% hint style="warning" %}
**Important notes.** Output token and output amount are helpful, but they are not the main source of truth. Obsidian verifies partner trades primarily from nested router calldata and token transfer logs. Quest volume is tracked from the net amount that actually reached Obsidian after any partner-side skim.
{% endhint %}

### 3.3 Partner API Key

After your contract flow is approved, Obsidian will issue a partner API key.

| Item               | Value                                              |
| ------------------ | -------------------------------------------------- |
| Header name        | `x-obsidian-partner-key`                           |
| Header format      | `clientId.secret`                                  |
| Default rate limit | `60` requests per minute per key                   |
| Scope              | Approved chain IDs and approved contract addresses |

### 3.4 Submit Transaction Hashes

**Route**

```http
POST /v1/partners/trades/ingest
```

**Headers**

{% hint style="warning" %}
**Implementation note.** Call this route from your backend, worker, or serverless function. Do not expose x-obsidian-partner-key in frontend or browser code. Store the key in an environment variable or server-side secret.
{% endhint %}

```
x-obsidian-partner-key: clientId.secret
Content-Type: application/json
```

**Request body**

```json
{
  "chainId": 25,
  "txHashes": [
    "0x1111111111111111111111111111111111111111111111111111111111111111",
    "0x2222222222222222222222222222222222222222222222222222222222222222"
  ]
}
```

**Request rules**

* Maximum **4 unique transaction hashes** per request
* Hashes must be standard `0x`-prefixed transaction hashes
* Re-submitting the same successful transaction is safe and idempotent
* Default partner-key rate limit is `60` requests per minute unless Obsidian provisions a different limit for your project

**Response shape**

```json
{
  "partnerProjectSlug": "example-project",
  "partnerKeyId": "abc123",
  "chainId": 25,
  "submittedCount": 2,
  "processedCount": 1,
  "skippedCount": 1,
  "failedCount": 0,
  "results": [
    {
      "txHash": "0x1111111111111111111111111111111111111111111111111111111111111111",
      "status": "processed",
      "message": "Swap details processed successfully."
    },
    {
      "txHash": "0x2222222222222222222222222222222222222222222222222222222222222222",
      "status": "skipped_existing",
      "message": "Transaction already processed."
    }
  ]
}
```

**Result statuses**

| Status             | Meaning                                                                        |
| ------------------ | ------------------------------------------------------------------------------ |
| `processed`        | The trade was verified and written into Obsidian trade tracking                |
| `skipped_existing` | The transaction was already processed earlier                                  |
| `failed`           | The hash was received but could not be verified for the approved partner scope |

## 4. Final Notes

* Direct swap integration routes are public.
* Partner quest tracking ingestion is authenticated and only enabled after contract review.
* If your project wants quest tracking, **provide real sample transactions early.** That shortens onboarding more than anything else.

## 5. Building with AI

This API was designed to be readable by coding assistants. The endpoint summaries, request bodies, mapping rules, and examples are written so that an AI agent can scaffold a working integration with minimal hand-holding.

This section gives you the patterns, prompts, and guardrails to do it well.

### 5.1 Why This Doc Is AI-Friendly

The structure of this guide is deliberate:

| Pattern                      | Why it matters for AI                                              |
| ---------------------------- | ------------------------------------------------------------------ |
| Stable endpoint tables       | Models can cite exact routes without hallucinating paths           |
| Explicit request bodies      | Models can copy the shape instead of guessing field names          |
| Quote-to-trade mapping table | The most error-prone step is spelled out as a direct map           |
| Constant values in tables    | Router, WCRO, native placeholder, and chain ID are easy to extract |
| Compact end-to-end example   | Models can use it as a single-shot reference for full flows        |

When you hand this doc to an assistant, you do not need to re-explain any of these. They are already in a form the model can use.

### 5.2 Recommended Workflow

A simple loop that works for most partner integrations:

```
1. Paste the relevant section of this doc into your assistant.
2. Tell it your stack (Next.js, ethers.js, viem, wagmi, etc.).
3. Tell it what you want (quote, swap, token picker, ingest worker).
4. Ask for a single self-contained module first.
5. Have it write a test against the example payloads in Section 1.3.
6. Iterate.
```

The shorter and more specific the prompt, the better the output.

### 5.3 Starter Prompts

Copy, adapt, and paste. Each prompt assumes you have also provided the relevant doc section to the assistant.

**Quote and Swap (Frontend)**

```
Using the Powered by Obsidian Partner API docs I provided, generate a
TypeScript module for a Next.js app using viem that:

- Accepts an input token, output token, and input amount in human-readable form
- Calls POST /25/v1/getquote with the correct shape
- Maps the response to POST /v1/getswap using the mapping rules in section 1.2
- Returns { to, data, value, gasLimit } ready to pass to a wallet client
- Treats CRO as native using the zero address
- Uses 0.5% slippage as the default

Use exact field names from the docs. Do not invent fields.
```

**Token Picker (Frontend)**

```
Using the Powered by Obsidian Partner API docs, build a React component that:

- Fetches GET /25/v1/whitelisted-tokens on mount
- Renders a searchable token list with logo, symbol, name, and address
- Caches results in memory for the session
- Falls back gracefully if the request fails

Use the exact field names from section 2.
```

**Partner Ingest Worker (Backend)**

```
Using the Powered by Obsidian Partner API docs, write a Node.js worker that:

- Watches our contract for Swap events
- Buffers tx hashes
- Every 30 seconds (or when 4 hashes are buffered) calls
  POST /v1/partners/trades/ingest with the x-obsidian-partner-key header
- Logs processed, skipped, and failed counts
- Respects the 60 req/min rate limit
- Reads the partner key from an env var, never hardcoded

Use exact field names from section 3.4.
```

**Contract Scaffolding (Quest Tracking Onboarding)**

```
Using section 3 of the Powered by Obsidian Partner API docs, write a Solidity
event and a swap function that:

- Emits the preferred event shape with user, inputToken, inputAmount, feeAmount,
  outputToken, outputAmount
- Forwards the swap to the Obsidian router with the end user as the nested recipient
- Skims a 0.25% fee in the input token before forwarding

Make the user attribution work via the indexed `user` argument in the event.
```

### 5.4 Guardrails When Building with AI

Coding assistants are excellent at scaffolding and weak at on-chain reasoning. Use these guardrails:

| Guardrail                                          | Why                                                               |
| -------------------------------------------------- | ----------------------------------------------------------------- |
| Always verify the router address from this doc     | Models sometimes invent or stale-cache addresses                  |
| Always confirm the chain ID is `25`                | Some models default to mainnet-style chain IDs                    |
| Always test with small amounts on Cronos first     | An incorrect calldata field will silently route wrong             |
| Pin the exact `tradeType` values                   | `0` is for the quote route, `"EXACT_INPUT"` is for the swap route |
| Treat `gasEstimateInUSD` as informational only     | Documented as non-authoritative                                   |
| Never let an AI agent hold your partner API key    | Use a server-side proxy and inject the key from env               |
| Diff AI output against the example payloads in 1.3 | Field-by-field comparison catches almost every shape error        |

### 5.5 Suggested Project Files for AI-First Builders

If you are starting a new partner integration, this is a clean repo layout that AI assistants tend to handle well:

```
your-partner-app/
├── docs/
│   └── obsidian-partner-api.md    ← drop this guide in
├── lib/
│   ├── obsidian/
│   │   ├── quote.ts               ← POST /:chainId/v1/getquote
│   │   ├── swap.ts                ← POST /v1/getswap
│   │   ├── tokens.ts              ← GET /:chainId/v1/whitelisted-tokens
│   │   └── mapping.ts             ← quote → trade mapping helpers
├── server/
│   └── ingest-worker.ts           ← POST /v1/partners/trades/ingest
├── contracts/
│   └── PartnerRouter.sol          ← optional middleman contract
└── tests/
    └── obsidian.spec.ts           ← uses the 1.3 example payloads
```

Point your assistant at this structure and ask it to fill in one file at a time. Small, focused prompts beat one giant prompt every time.

### 5.6 When To Ask Obsidian Instead Of An AI

Some things an AI cannot answer for you. Open a Discord ticket if:

* You need quest tracking enabled for a custom middleman contract
* You need a higher rate limit than `60` requests per minute
* Your event attribution does not match any of the supported attribution sources
* You see consistent `failed` statuses in your ingest responses
* You want to discuss flows not documented in this guide

Real sample transactions speed up every one of these conversations.


---

# 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://wiki.obsidian.finance/dex/api-docs-v2.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.
