> For the complete documentation index, see [llms.txt](https://wiki.obsidian.finance/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://wiki.obsidian.finance/dex/api-docs-v2.md).

# Build with Obsidian

Build swap interfaces and trading tools with quotes, swap preparation, optional partner fees and trade reporting.

Build swap interfaces and trading tools with Obsidian's quotes, swap preparation, token metadata and optional trade reporting.

The basic flow is **quote → prepare → user signs → confirm**. Quotes and swap preparation are public. Add your fee wallet and rate to collect a partner fee. A backend partner key enables higher request limits and trade reporting.

Start with the [Swap API Quick Start](https://wiki.obsidian.finance/dex/api-docs-v2/quick-start) for a complete example with an optional partner fee. Replace application wallet and contract placeholders with your own values.

### Quick reference

| Item                       | Value                                        |
| -------------------------- | -------------------------------------------- |
| API base URL               | `https://api.obsidian.finance`               |
| Network                    | Cronos mainnet, chain ID `25`                |
| Native CRO address         | `0x0000000000000000000000000000000000000000` |
| Wrapped CRO (WCRO)         | `0x5C7F8A570d578ED84E63fdFA7b1eE72dEae1AE23` |
| Obsidian router            | `0x1189331089b6ca8beA989C1F2fFd0EfAdCd33a69` |
| Partner-fee settlement     | `0x8b6a0183558f256051bdAD08d438626f96CBc983` |
| Backend partner-key header | `x-obsidian-partner-key: YOUR_API_KEY`       |

| Operation               | Method and path                       | Access                                                |
| ----------------------- | ------------------------------------- | ----------------------------------------------------- |
| Get token metadata      | `GET /:chainId/v1/whitelisted-tokens` | Public                                                |
| Get a quote             | `POST /:chainId/v1/getquote`          | Public; optional backend partner key                  |
| Prepare a swap          | `POST /v1/getswap`                    | Public for direct swaps; optional backend partner key |
| Report confirmed trades | `POST /v1/partners/trades/ingest`     | Backend partner key required                          |

Examples use **exact input**: the user chooses how much to spend. The API returns unsigned data; your application connects the wallet and the user signs.

### 1. Choose your integration

| Integration                  | Your additional fee      | Execution                                                                  |
| ---------------------------- | ------------------------ | -------------------------------------------------------------------------- |
| Direct API swap              | None                     | Get a quote, prepare the swap and have the user sign                       |
| Direct API partner fee       | Up to 1% of gross output | Include your fee wallet and rate; sign the returned settlement transaction |
| Your contract: fee on input  | Taken before routing     | Your contract forwards the remaining input to the router                   |
| Your contract: fee on output | Taken after routing      | Your contract receives router output, takes its fee and pays the user      |

Obsidian's application fee is **0.4%**. For the direct API partner-fee option, your fee is additional, up to **1%**, for a maximum **1.4%** combined service fee. See [Direct API partner fees](#direct-api-partner-fees) for the calculation.

For your own contract, follow [Custom contract integrations](#id-9.-custom-contract-integrations). Its approvals, quoted amount, recipient and signed transaction can differ.

#### Requesting a partner key

Open an Obsidian Discord support ticket with your application details and expected usage. Solo developers can request access too.

For your own contract, include its address, verified source/ABI and fee flow.

Obsidian will provide your API key after onboarding. Store it securely on your backend and attach it in the header shown above. Keep it out of browser code and public repositories. Contact Obsidian if you need a replacement.

### 2. API keys and rate limits

| Operation        | Without a key         | Default with a valid partner key |
| ---------------- | --------------------- | -------------------------------- |
| Quote            | 60 requests/minute/IP | 300 requests/minute/key          |
| Swap preparation | 60 requests/minute/IP | 60 requests/minute/key           |
| Token list       | 60 requests/minute/IP | Same public IP limit             |
| Trade ingestion  | Key required          | 60 requests/minute/key           |

Higher rate limits can be accommodated if needed. Open an Obsidian Discord support ticket to discuss your requirements.

Trade ingestion accepts up to **four unique transaction hashes per request**.

Use the response's rate-limit headers to track usage. On `429`, wait for `Retry-After` before retrying.

Browser applications can call public routes directly. To use a key for quoting or preparation, send requests through your backend and attach the key there. The same key can be used for reporting.

### 3. Get token metadata

`GET https://api.obsidian.finance/25/v1/whitelisted-tokens`

This returns the public Cronos token list from the global catalogue, including native CRO. No API key is required.

```js
const tokenResponse = await fetch(
  "https://api.obsidian.finance/25/v1/whitelisted-tokens"
);
if (!tokenResponse.ok) throw new Error(`Token list failed: ${tokenResponse.status}`);
const tokenList = await tokenResponse.json();
const tokens = tokenList.tokens;

// In your app, this address comes from the user's token selection.
const selectedOutputAddress = "YOUR_OUTPUT_TOKEN_ADDRESS";
const outputToken = tokens.find(token =>
  token.chainId === 25 &&
  token.address.toLowerCase() === selectedOutputAddress.toLowerCase()
);
if (!outputToken) throw new Error("Select a token from the Cronos token list.");
```

Each token includes `address`, `chainId`, `name`, `symbol`, `decimals` and `logoURI`. Use its actual metadata when requesting a quote.

Native CRO uses the zero address; WCRO uses its contract address. Cache the token list for your picker and request quotes to check route availability.

### 4. Request an exact-input quote

`POST https://api.obsidian.finance/25/v1/getquote`

This example sells **1 native CRO** for the token selected above. `partnerFee` is optional: leave it `undefined` for a normal swap, or set the shown object to collect 0.6% in your configured fee wallet.

```js
const partnerFee = undefined;
// To collect 0.6%, replace the line above with:
// const partnerFee = { recipient: "YOUR_FEE_WALLET_ADDRESS", basisPoints: 60 };

const quoteRequest = {
  chainId: 25,
  tradeType: 0,
  amount: {
    currency: {
      chainId: 25,
      address: "0x0000000000000000000000000000000000000000",
      decimals: 18,
      symbol: "CRO",
      name: "Cronos"
    },
    value: "1000000000000000000"
  },
  currency: {
    chainId: 25,
    address: outputToken.address,
    decimals: outputToken.decimals,
    symbol: outputToken.symbol,
    name: outputToken.name
  },
  poolTypes: [0, 1],
  venueScope: "all",
  maxHops: 4,
  maxSplits: 3,
  ...(partnerFee?.basisPoints > 0 ? { partnerFee } : {})
};

const quoteResponse = await fetch("https://api.obsidian.finance/25/v1/getquote", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify(quoteRequest)
});
if (!quoteResponse.ok) throw new Error(`Quote failed: ${quoteResponse.status}`);
let quote = await quoteResponse.json();
```

| Parameter         | Meaning                                                                                              |
| ----------------- | ---------------------------------------------------------------------------------------------------- |
| `chainId`         | `25` for this guide                                                                                  |
| `tradeType`       | `0` for exact input                                                                                  |
| `amount.currency` | Input token metadata; `address`, `decimals` and `symbol` are required                                |
| `amount.value`    | Positive raw input amount as a decimal integer string                                                |
| `currency`        | Output token metadata; `address`, `decimals` and `symbol` are required                               |
| `poolTypes`       | `[0, 1]` allows V2 and V3, including mixed paths; `[0]` is V2 only, `[1]` is V3 only                 |
| `venueScope`      | `"all"` for all supported venues, or `"obsidian"` for supported Obsidian venues                      |
| `maxHops`         | Optional maximum hops, `1`–`5`; example uses `4`                                                     |
| `maxSplits`       | `3` allows the normal split search; `0` requests one route, `2` allows up to two                     |
| `partnerFee`      | Optional direct API fee object; integer `basisPoints` from `0` to `100` and a valid receiving wallet |

Amounts use the token's smallest units. Convert decimal user inputs with an integer-based token-unit library, not floating-point arithmetic. The routing options allow a route type; they do not guarantee that V3 or a split will be best for every request.

Pass the **complete quote response** to preparation, including `routes`, fee metadata and `quoteContext`.

| Response field                            | Meaning                                                             |
| ----------------------------------------- | ------------------------------------------------------------------- |
| `inputAmount`                             | Input currency and raw amount                                       |
| `outputAmount.value`                      | Expected user output after service fees; do not subtract them again |
| `grossOutputAmount.value`                 | Aggregate output before service fees                                |
| `routes`                                  | Paths, pools and exact input allocations                            |
| `quoteContext`                            | Chain and block snapshot; preserve unchanged                        |
| `partnerFee`, `feeBreakdown`, `execution` | Additional metadata for a positive direct API partner fee           |
| `gasEstimate`                             | Route estimate; final preparation is still required                 |

### 5. Approve ERC20 input when needed

Native CRO needs no ERC20 approval. Keep enough CRO for the input **and gas**. For ERC20 input, the user must approve the correct spender before preparation:

| Flow                   | User's ERC20 spender                                     |
| ---------------------- | -------------------------------------------------------- |
| Ordinary direct swap   | Obsidian router from the quick reference                 |
| Direct API partner fee | Settlement contract at `quote.execution.approvalSpender` |
| Your own contract      | Your reviewed contract, according to its funding flow    |

Approve the required input amount, confirm the approval and request a fresh quote before preparation.

### 6. Prepare the direct API transaction

`POST https://api.obsidian.finance/v1/getswap`

The following covers direct swaps with or without your own fee. `walletAddress` is the **actual signing wallet** from your wallet connection. `quote` is the complete fresh response from Section 4. If you requested a partner fee, repeat the **same** `partnerFee` object here.

```js
const preparationResponse = await fetch("https://api.obsidian.finance/v1/getswap", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({
    chainId: 25,
    userAddress: walletAddress,
    quote,
    ...(partnerFee?.basisPoints > 0 ? { partnerFee } : {}),
    options: {
      recipient: walletAddress,
      slippageTolerance: { numerator: "5", denominator: "1000" },
      deadlineOrPreviousBlockhash: String(Math.floor(Date.now() / 1000) + 600)
    }
  })
});
const prepared = await preparationResponse.json();
if (!preparationResponse.ok) {
  throw new Error(prepared.message || prepared.error || "Swap preparation failed.");
}
```

`5 / 1000` allows 0.5% slippage. The deadline is a Unix timestamp in **seconds**, here ten minutes in the future. Preparation simulates from `userAddress`, so balance and allowance must already be sufficient. Resolve any preparation failure before proceeding.

| Response           | Without a partner fee  | With a partner fee                         |
| ------------------ | ---------------------- | ------------------------------------------ |
| Transaction target | `routerAddress`        | **`transaction.to`** (settlement contract) |
| Calldata           | `calldata`             | **`transaction.data`**                     |
| Native value       | `value`                | **`transaction.value`**                    |
| Gas limit          | `estimatedGas`         | **`transaction.gasLimit`**                 |
| User output        | `expectedOutputAmount` | `expectedOutputAmount`, after both fees    |

For a positive partner fee, `transaction.to` is the settlement contract in the quick reference. Use the returned **`transaction`** fields together; do not substitute `routerAddress` for `transaction.to`.

### 7. Have the user sign and confirm

Show the output, fees, minimum received and gas information for review. Then, only after the user confirms, send the transaction through their wallet. The complete [Quick Start signing example](https://wiki.obsidian.finance/dex/api-docs-v2/quick-start#id-4.-review-sign-and-confirm) handles both direct API modes.

Before signing, check the connected chain and wallet, recipient, input amount and native value against the user's request. Use the API's returned transaction target and data.

Save and display the returned transaction hash **before waiting for a receipt**. Check receipt status. If confirmation times out, inspect the hash before asking the user to send again.

#### Direct API partner fees

Add `partnerFee` to **both** quote and preparation requests to collect a fee in your wallet. No API key or contract deployment is required.

The user's wallet calls Obsidian's settlement contract. It executes the swap through the router, then pays Obsidian, your fee wallet and the user in the same transaction. The API prepares this transaction for signing.

```json
{
  "partnerFee": {
    "recipient": "YOUR_FEE_WALLET_ADDRESS",
    "basisPoints": 60
  }
}
```

`60` basis points means **0.6%**. Use a valid receiving wallet and an integer from `0` to `100` (up to 1%). Omit the object or use zero for an ordinary swap. Request a new quote if the wallet or rate changes.

Obsidian receives 0.4% and your wallet receives your chosen rate, both from gross output. For 1,000 output tokens and a 0.6% partner fee, Obsidian receives 4, you receive 6 and the user receives 990. Amounts round down in token units. The maximum combined service fee is **1.4%**. Network gas and pool fees are separate.

Fees are paid in the output asset. `feeBreakdown` provides their amounts for display, and `quote.outputAmount.value` already includes **both** fees. Do not deduct either again.

Use a fresh quote, currently within **30 seconds of its snapshot**, for this mode and for supported forwarder preparation. Refresh after approvals or expiry.

This mode supports Cronos exact-input swaps with standard native/ERC20 assets. Transfer-tax and rebasing tokens are not supported. Do not combine `partnerFee` with `partnerContractAddress`.

Report the confirmed swap hash through the ingestion endpoint if trade reporting is enabled for your integration.

### 8. Report confirmed trades from your backend

`POST https://api.obsidian.finance/v1/partners/trades/ingest`

After a successful receipt, send the hash to your backend and report it with your partner key. This server-side example accepts one confirmed hash; the API allows up to four unique hashes per request.

```js
async function reportConfirmedTrade(confirmedTxHash) {
  const partnerKey = process.env.OBSIDIAN_PARTNER_KEY;
  if (!partnerKey) throw new Error("Configure your backend partner API key.");

  const response = await fetch("https://api.obsidian.finance/v1/partners/trades/ingest", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "x-obsidian-partner-key": partnerKey
    },
    body: JSON.stringify({ chainId: 25, txHashes: [confirmedTxHash] })
  });
  const report = await response.json();
  if (!response.ok) throw new Error(report.message || report.error || `Reporting failed: ${response.status}`);
  for (const result of report.results) {
    if (result.status === "failed") console.error(result.txHash, result.message);
  }
  return report;
}
```

| Per-hash status    | Meaning                                                             |
| ------------------ | ------------------------------------------------------------------- |
| `processed`        | Verified and processed for trade/reward tracking                    |
| `skipped_existing` | Already processed; retrying does not award it again                 |
| `failed`           | Inspect the message and resolve or retry the failure as appropriate |

An HTTP `200` can contain failed hashes. Inspect `failedCount` and each result; retry temporary failures with backoff. Repeated reporting does not award the same trade twice.

Report the confirmed swap transaction, including the **outer hash** when using your own contract. Do not report approvals. Obsidian verifies execution, fees and the end-user wallet before crediting eligible trades. Reporting does not guarantee rewards.

### 9. Custom contract integrations

To report trades through your own contract, open an Obsidian Discord support ticket with its address, verified source/ABI, fee flow and an example transaction identifying the end-user wallet.

#### Fee on input and fee on output

These are contract fee models, separate from the direct API `partnerFee` option.

| Detail                         | Fee on input                             | Fee on output                                                                        |
| ------------------------------ | ---------------------------------------- | ------------------------------------------------------------------------------------ |
| Amount to quote                | Net amount forwarded to the router       | Full amount forwarded to the router                                                  |
| Example with a 1% contract fee | User supplies 100 input tokens; quote 99 | Quote the full input; if router output after its fee is 100 tokens, user receives 99 |
| Router output recipient        | As required by the reviewed contract     | Usually your contract, so it can take its fee                                        |
| User's ERC20 spender           | Your contract, if it pulls the input     | Your contract, if it pulls the input                                                 |
| Signed target                  | Your contract                            | Your contract                                                                        |

Use your contract's exact integer rounding. Obsidian's fee is already included in the normal quote output. A fee on input changes the **quoted input**; a fee on output changes the **final user output**.

Confirm preparation support for your contract with Obsidian. Registration for reporting alone does not enable `partnerContractAddress`. Fee-on-input contracts need their own preparation instructions; the example below is for fee on output.

#### Worked example for a supported forwarder

This example uses a supported **fee-on-output** forwarder and quotes the full native CRO input. For ERC20 input, use the token's metadata and confirm the user's approval to your forwarder and its router allowance first.

**Backend: quote and prepare.** `outputToken` is the selected catalogue token, `walletAddress` is the actual signing wallet, and `amountInRaw` is the full raw native input amount. The key and supported contract are your backend configuration.

```js
async function prepareSupportedForwarder({ walletAddress, outputToken, amountInRaw }) {
  const partnerKey = process.env.OBSIDIAN_PARTNER_KEY;
  const forwarderAddress = process.env.APPROVED_FORWARDER_ADDRESS;
  if (!partnerKey || !forwarderAddress) throw new Error("Configure your key and supported forwarder.");

  async function post(path, body) {
    const response = await fetch("https://api.obsidian.finance" + path, {
      method: "POST",
      headers: { "Content-Type": "application/json", "x-obsidian-partner-key": partnerKey },
      body: JSON.stringify(body)
    });
    const data = await response.json();
    if (!response.ok) throw new Error(data.message || data.error || `Request failed: ${response.status}`);
    return data;
  }

  const quote = await post("/25/v1/getquote", {
    chainId: 25,
    tradeType: 0,
    amount: {
      currency: {
        chainId: 25,
        address: "0x0000000000000000000000000000000000000000",
        decimals: 18,
        symbol: "CRO",
        name: "Cronos"
      },
      value: amountInRaw
    },
    currency: {
      chainId: 25,
      address: outputToken.address,
      decimals: outputToken.decimals,
      symbol: outputToken.symbol,
      name: outputToken.name
    },
    poolTypes: [0, 1],
    venueScope: "all",
    maxHops: 4,
    maxSplits: 3
  });

  const prepared = await post("/v1/getswap", {
    chainId: 25,
    partnerContractAddress: forwarderAddress,
    userAddress: walletAddress,
    quote,
    options: {
      slippageTolerance: { numerator: "5", denominator: "1000" },
      deadlineOrPreviousBlockhash: String(Math.floor(Date.now() / 1000) + 600)
    }
  });
  if (!prepared.transaction) throw new Error("No prepared outer transaction returned.");
  return prepared;
}
```

Leave `options.recipient` unset: preparation sends the router output to your forwarder. Keep `userAddress` as the signing wallet, omit `partnerFee` and keep your API key on the backend.

**Frontend: sign the returned outer transaction.** The clients and `cronos` chain are initialized as in the Quick Start. `prepared` is your backend's response.

```js
const expectedForwarder = "YOUR_APPROVED_FORWARDER_ADDRESS";
const outer = prepared.transaction;
const [walletAddress] = await walletClient.getAddresses();
if (!outer || prepared.chainId !== 25 || await walletClient.getChainId() !== 25
  || outer.to.toLowerCase() !== expectedForwarder.toLowerCase()
  || outer.from.toLowerCase() !== walletAddress.toLowerCase()) {
  throw new Error("Prepared transaction does not match the wallet or forwarder.");
}

const feeFields = outer.maxFeePerGas !== undefined
  ? { maxFeePerGas: BigInt(outer.maxFeePerGas), maxPriorityFeePerGas: BigInt(outer.maxPriorityFeePerGas) }
  : { gasPrice: BigInt(outer.gasPrice) };

// Run only after the user has reviewed the amounts and confirmed.
const txHash = await walletClient.sendTransaction({
  account: walletAddress,
  chain: cronos,
  to: outer.to,
  data: outer.data,
  value: BigInt(outer.value),
  gas: BigInt(outer.gasLimit),
  ...feeFields
});
console.log("Submitted transaction:", txHash); // Show and save this hash immediately.
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash, confirmations: 2 });
if (receipt.status !== "success") throw new Error(`Swap reverted: ${txHash}`);
```

Review the input, native value, expected output and minimum before signing. Use **`transaction`** for the outer call; top-level `calldata` is the inner router call. `expectedOutputAmount` and `minimumOutputAmount` already account for the supported contract's fees; do not deduct them again.

After confirmation, follow [Report confirmed trades](#id-8.-report-confirmed-trades-from-your-backend) with the **outer hash** and the key scoped to your approved contract.

### 10. Error handling

| Response                              | Action                                                                                             |
| ------------------------------------- | -------------------------------------------------------------------------------------------------- |
| `400`                                 | Correct request fields, metadata, chain or recipients                                              |
| `401`                                 | Supply the required backend partner key, or correct its header format                              |
| `403`                                 | Check the key's enabled state and allowed chain/contract scope                                     |
| Quote `404`                           | No route found; check the pair and amount                                                          |
| `409`                                 | Refresh the quote or resolve changed contract/context settings                                     |
| `422`                                 | Inspect `code` and `message`; resolve token support, balance, allowance, gas or simulation failure |
| `429`                                 | Honor `Retry-After`; reduce concurrency and use bounded backoff                                    |
| `503` with `PARTNER_FEES_UNAVAILABLE` | Requested direct partner-fee mode is unavailable on this chain                                     |
| Other `5xx`                           | Treat as a service failure; retry requests with bounded backoff where safe                         |
| Ingestion `200` with failed hashes    | Inspect every result; HTTP success does not mean every trade succeeded                             |

If a wallet submission times out, check the saved transaction hash before asking the user to send again.
