> For the complete documentation index, see [llms.txt](https://titan-exchange.gitbook.io/titan/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://titan-exchange.gitbook.io/titan/developer-doc/special-order-types/order-types/dca.md).

# DCA

Recurring swaps on a fixed schedule — config fields, progress tracking, and modification.

**A `dca` order spends a fixed `amountPerCycle` of the input mint on a schedule until `totalAmount` is exhausted.** The user funds the whole `totalAmount` up front in one deposit; Titan holds it in their manager and swaps a slice at each cycle.

DCA is the only order type that can be modified after creation, and the only one that produces more than one execution.

## Config

```json
{
  "orderType": "dca",
  "userPubkey": "<user's external Solana wallet>",
  "config": {
    "inputMint":  "So11111111111111111111111111111111111111112",
    "outputMint": "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
    "totalAmount":           "1000000000",
    "amountPerCycle":        "100000000",
    "cycleFrequencySeconds": 86400,
    "minOutputPerCycle":     "0",
    "maxOutputPerCycle":     "0",
    "startAt":               1735689600,
    "expiresAt":             1767225600
  }
}
```

| Field                   | Required | Notes                                                                                                  |
| ----------------------- | -------- | ------------------------------------------------------------------------------------------------------ |
| `inputMint`             | ✅        | SPL mint address. Must differ from `outputMint`.                                                       |
| `outputMint`            | ✅        | SPL mint address.                                                                                      |
| `totalAmount`           | ✅        | Total input the user spends over the order's lifetime. Funded in a single deposit at creation.         |
| `amountPerCycle`        | ✅        | Input spent per cycle. Must be worth at least **$10 USD** by default — your tenant's configured floor. |
| `cycleFrequencySeconds` | ✅        | Seconds between cycles. Minimum **60**.                                                                |
| `minOutputPerCycle`     | ❌        | The cycle aborts if the quote comes in below this.                                                     |
| `maxOutputPerCycle`     | ❌        | The cycle aborts if the quote comes in above this.                                                     |
| `startAt`               | ❌        | Unix seconds. The first cycle waits until this time.                                                   |
| `expiresAt`             | ❌        | Unix seconds. The order auto-expires at this time.                                                     |

The top-level fields — `userPubkey`, `outputRecipientAddress`, `platformFee`, `onboardIfNeeded` — are shared by every order type and documented in [Creating Orders](/titan/developer-doc/special-order-types/guides/creating-orders.md).

{% hint style="warning" %}
`amountPerCycle` is priced at request time to enforce the minimum. USDC and USDT count as $1.00; everything else goes through the oracle. Below the floor returns `400 MIN_NOTIONAL_NOT_MET` with `details` echoing `mint`, `amount`, `usdCents`, and `requiredCents`. If the oracle can't price the mint you get `503 PRICE_ORACLE_UNAVAILABLE` — transient, retry shortly.
{% endhint %}

Titan sponsors the Solana network fees on recurring cycle executions, so the manager never needs SOL to keep the schedule running.

## Read progress

`GET /dca/{orderId}` returns the order with its progress counters filled in:

```json
{
  "cyclesCompleted": 3,
  "totalCycles": 10,
  "amountSpent":    "300000000",
  "amountReceived": "150000000",
  "nextExecutionAt": "2025-01-04T00:00:00.000Z",
  "lastExecutionAt": "2025-01-03T00:00:00.000Z",
  "lastExecutionTxHash": "<solana tx signature or null>"
}
```

`nextExecutionAt` is what makes DCA cheap to poll — you know when the next cycle lands, so you don't need a tight loop. `GET /orders/{orderId}/executions` gives you one row per cycle with `executionType: "dca_cycle"`, most recent first, capped at 500 rows. The full field list is in [Order & Execution Schema](/titan/developer-doc/special-order-types/reference/schema.md).

Returns `404 NOT_FOUND` if the order doesn't exist, isn't a DCA order, or isn't this user's.

## Modify

`PATCH /dca/{orderId}` edits an `active` or `paused` DCA order — this is the one order type that supports it. Editable fields: `amountPerCycle`, `cycleFrequencySeconds`, `totalCycles`, `totalAmount`, `minOutputPerCycle`, `maxOutputPerCycle`.

Most edits apply in place:

```json
{ "success": true, "data": { "requiresTransaction": false, "order": { "…": "…" } } }
```

Changing `totalAmount` is different, because the difference has to be deposited or withdrawn on-chain. That turns modify into a two-step flow like creation, and the response carries an unsigned transaction:

```json
{
  "success": true,
  "data": {
    "requiresTransaction": true,
    "transactionType": "deposit",
    "transactionAmount": "1000000000",
    "newTotalAmount":    "2000000000",
    "transaction": "<base64 unsigned tx>",
    "encoding": "base64",
    "memoId": "dca-modify-9b3f1ad0-…",
    "cyclesCompleted": 3,
    "config": { "…": "echoed config you submitted" }
  }
}
```

Have the user sign it, then submit to `POST /dca/{orderId}/modify/confirm` with the same `cyclesCompleted` and `config` you got back.

{% hint style="warning" %}
Cycle executions pause while a modification is being signed — the user's signed `totalAmount` can't be applied to a different on-chain state than the one they reviewed. The lock auto-releases after **30 seconds**; if it expires before confirm you get `409 LOCK_EXPIRED` and re-run the modify intent. If a cycle lands mid-signature, `cyclesCompleted` won't match and confirm returns `400 MODIFICATION_ERROR` — re-fetch the order and let the user re-confirm.
{% endhint %}

| HTTP | `error.code`                                   | When                                                                                                                                       |
| ---- | ---------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| 400  | `VALIDATION_ERROR`                             | Bad config.                                                                                                                                |
| 400  | `MIN_NOTIONAL_NOT_MET`                         | New `amountPerCycle` below your tenant's floor. Only checked when `amountPerCycle` changes.                                                |
| 400  | `MODIFICATION_ERROR`                           | A server-side guard rejected the change (e.g. shrinking `totalAmount` below what's already been spent), or `cyclesCompleted` didn't match. |
| 400  | `INVALID_REQUEST`                              | `/modify/confirm` body shape invalid.                                                                                                      |
| 400  | `TRANSACTION_EXPIRED` / `TRANSACTION_TAMPERED` | The 5-minute window elapsed, or the signed tx doesn't match the unsigned one.                                                              |
| 404  | `NOT_FOUND`                                    | Order missing or not yours.                                                                                                                |
| 409  | `LOCK_FAILED`                                  | Another modification is already in progress.                                                                                               |
| 409  | `LOCK_EXPIRED`                                 | The 30-second lock elapsed before confirm arrived.                                                                                         |
| 503  | `PRICE_ORACLE_UNAVAILABLE`                     | Couldn't price a non-stable `inputMint`. Retry shortly.                                                                                    |

## Lifecycle

```
intent ──confirm──▶ active ──cycle..N──▶ completed
                      │
                      ├─ pending  ── (only with a future config.startAt;
                      │               becomes active at that time)
                      │
                      ├─ pause/resume ↔  paused
                      │
                      ├─ executing  ── (during a cycle)
                      │
                      ├─ pending_modification  ── (during modify signing)
                      │
                      ├─ failed  ── (retry budget exhausted) ── unspent input auto-returned
                      │              (retry ──▶ active only in the brief pre-return window)
                      │
                      ├─ cancelled  ── (user cancel)
                      │
                      └─ expired   ── (config.expiresAt reached)
```

Three behaviours are DCA-specific. A future `config.startAt` parks the order in `pending` until that time — it's the only way an order reaches that status, and without `startAt` confirm goes straight to `active`. `POST /orders/{orderId}/retry` can flip a `failed` order back to `active`, but only in the brief window before its unspent input is auto-returned. And an `expired` DCA order is **not** auto-returned — unlike an expired trigger order, you have to call [order-level withdraw](/titan/developer-doc/special-order-types/guides/withdrawals.md#order-level-withdrawals) for it.

## Related pages

* [Creating Orders](/titan/developer-doc/special-order-types/guides/creating-orders.md) — the two-step flow and the shared top-level fields
* [Managing Orders](/titan/developer-doc/special-order-types/guides/managing-orders.md) — pause, resume, retry, cancel
* [Lifecycle & Polling](/titan/developer-doc/special-order-types/guides/lifecycle.md) — statuses and polling cadence
