For the complete documentation index, see llms.txt. This page is also available as Markdown.

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

{
  "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 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:

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.

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:

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:

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

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

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 for it.

Last updated