Appearance
Intent API
Submit an Intent — what you want to exchange and the constraints you accept — to the Solver Network. The solver computes an execution path and either accepts it (reserving liquidity and starting settlement) or rejects it with a reason.
Submission is single-step: the response is terminal (accepted or rejected) and there is no separate confirm step — see Status & lifecycle and Cancellation. To lock a price before committing, request a firm quote first and attach its quoteId (see Quote API → Firm quotes, and Amount bounds and firm quotes).
Submit Intent
POST /intentsSynchronous; responds 202 Accepted. The body is an accepted or rejected object. On acceptance the solver reserves balance, derives an HTLC hashlock, and starts the DvP settlement workflow.
Request Body
json
{
"intentId": "INT-2F8A3B1C",
"user": "0xabc...def",
"inputAsset": {
"type": "erc20",
"symbol": "USDC",
"chain": "ethereum",
"address": "0xA0b8...eB48"
},
"outputAsset": {
"type": "fiat",
"symbol": "KRW",
"currency": "KRW"
},
"inputAmount": "1000",
"minOutputAmount": "1400000",
"deadline": 1737123456789,
"settlementType": "hybrid",
"outputRecipient": "kr-bank-account-123",
"beneficiary": {
"account": "110-123-456789",
"name": "Hong Gildong",
"bank": "KEB Hana"
}
}| Field | Type | Required | Description |
|---|---|---|---|
intentId | string | Yes | Caller-supplied unique ID; also used as the API source/reply reference |
clientReference | string | No | Target. Your own correlation key for this transaction (e.g. your payment-pipeline ID). Bound at submission and echoed verbatim on every read surface — Status, Webhooks, Receipts, reconciliation. Independent of the Idempotency-Key header, which is transport-level retry safety (see Idempotency) |
user | string | Yes | Account or address that owns the Intent |
inputAsset | object | Yes | Asset you send. See AssetDescriptor |
outputAsset | object | Yes | Asset you receive. See AssetDescriptor |
inputAmount | string | Yes | Input amount you send (see Money & Amounts). In the generalized bounds model this is input.target == input.limit — see Amount bounds and firm quotes |
minOutputAmount | string | Yes | Minimum output you accept. Below this → below_minimum_output. In the bounds model this is output.limit |
quoteId | string | No | Reference to a locked firm quote (LP-committed via RFQ). When attached, the quoted rate and itemized fees are guaranteed for the quote's TTL. See Amount bounds and firm quotes |
deadline | number | Yes | Unix timestamp expiry |
settlementType | string | Yes | onchain, offchain, or hybrid |
outputRecipient | string | No | Third-party recipient. If set and ≠ user, this is a remittance and beneficiary is required |
beneficiary | object | Conditional | Required for a remittance, and for any fiat output. See Beneficiary |
actorType | string | No | retail or institution. Defaults to institution |
side | string | No | buy or sell. Defaults to sell |
metadata | object | No | Compliance / routing metadata. See Metadata |
settlementModeis not part of the request — the solver currently settlesatomic.
Amount bounds and firm quotes
The generalized bounds form and quoteId support destination-fixed payout and firm quote acceptance. The exact-input shape (inputAmount + minOutputAmount) remains supported as backward-compatible sugar.
The Intent amount model generalizes to two orthogonal axes (the same decomposition UniswapX uses — amounts as decaying ranges that can collapse to a point, and firmness supplied by an RFQ winner who commits to fill):
Axis 1 — amounts are bounds that can collapse to a point. Each side carries { target, limit }. When target == limit the amount is fixed (a limit order); when they differ, the amount decays from target toward limit over the intent's lifetime (an auction range).
jsonc
"input": { "target": "...", "limit": "..." }, // limit = the most you will pay (cap)
"output": { "target": "...", "limit": "..." } // limit = the least you accept (floor)| Mode | input | output | Meaning |
|---|---|---|---|
| Exact-input (default) | target == limit (fixed) | limit only (floor) | Fix what you send; solvers compete on output. inputAmount/minOutputAmount are sugar for this. |
| Exact-output | limit only (cap) | target == limit (fixed) | Recipient receives an exact amount — a bank wire with sender-paid fees (OUR). Solvers compete on the input within your cap. |
| Range (auction) | cap | decaying target → limit | UniswapX-style Dutch decay; first profitable solver fills. |
Axis 2 — firmness comes from a locked quote, not from the amount shape. Without quoteId, an Intent is best-effort: it competes in the auction and may expire unfilled. With quoteId referencing a firm RFQ quote, the quoting LP has committed the rate and itemized fees for the quote's TTL — the LP warehouses the spread risk (exactly like a UniswapX exclusive quoter) and settlement enforces delivery of the quoted output, so the fill is guaranteed within the TTL.
Where does
quoteIdcome from? The firm flow is quote-first — the two flows enter in a different order:Best-effort (auction): submit intent ──▶ solvers compete ──▶ fill or expire (no quoteId anywhere — the auction is the price discovery) Firm (RFQ-accept): ① request firm quote (Quote API) — "what will it cost?" ② receive quoteId + locked rate/fees/TTL — LP commits ③ submit intent WITH quoteId — "I accept this quote" ④ guaranteed fill within validUntilThe intent never triggers the RFQ; the client obtains the
quoteIdbefore submitting, and the intent that carries it is semantically the acceptance of that quote. This is the same handle-passing shape as the firm flow (POST /quotes/firm→quoteId→POST /intents) — except the handle is LP-committed rather than advisory.
Exact-output, best-effort (no quoteId — auction, may expire):
jsonc
{
"intentId": "INT-9C1D2E3F",
"user": "0xabc...def",
"inputAsset": { "type": "erc20", "symbol": "USDC", "chain": "ethereum", "address": "0xA0b8...eB48" },
"outputAsset": { "type": "fiat", "symbol": "USD", "currency": "USD" },
"input": { "limit": "100305000000" }, // cap: most you'll pay — 100,305 USDC (minor)
"output": { "target": "100000.00", "limit": "100000.00" }, // exactly USD 100,000.00
"deadline": 1737123456789,
"settlementType": "hybrid",
"outputRecipient": "us-bank-account-123",
"beneficiary": { "account": "...", "name": "Jane Doe", "bank": "..." }
}Exact-output, firm (locked quote attached — guaranteed within TTL):
jsonc
{
// ...same as above, plus:
"quoteId": "fq_9ab32c1f" // ← from the firm-quote response (Quote API → Firm quotes):
// locked allInRate, required input,
// itemized fees, validUntil 2026-07-15T12:00:30Z
}Amounts follow the Money & Amounts convention. For exact-output intents the accepted response returns the computed inputAmount; when a quoteId is attached, the fees breakdown is itemized from the locked quote (see Accepted Response). Firm quotes are issued by the Quote API RFQ flow — the fq_9ab32c1f above is the quoteId returned by the firm-quote response example in Quote API → Firm quotes.
AssetDescriptor
| Field | Type | Required | Description |
|---|---|---|---|
type | string | Yes | erc20, fiat, or native |
symbol | string | Yes | e.g. USDC, KRW, ETH |
address | string | No | ERC20 contract address (erc20) |
chain | string | No | e.g. ethereum (on-chain assets) |
currency | string | No | ISO 4217 code (fiat), e.g. KRW |
Beneficiary
| Field | Type | Required | Description |
|---|---|---|---|
account | string | Yes | Recipient account |
name | string | Yes | Recipient name |
bank | string | No | Recipient bank |
travelRuleData | object | No | Travel Rule payload |
Metadata
| Field | Type | Description |
|---|---|---|
senderKycHash | string | Sender KYC reference |
depositReference | string | Deposit reference for the fiat on-ramp leg |
currency | string | Settlement currency hint |
targetStablecoin | string | Stablecoin used for the fiat on-ramp leg |
travelRuleData | object | Travel Rule payload |
beneficiaryAccount | string | Deprecated — use top-level beneficiary.account |
beneficiaryName | string | Deprecated — use top-level beneficiary.name |
Accepted Response
json
{
"intentId": "INT-2F8A3B1C",
"status": "accepted",
"planId": "DVP-INT-2F8A",
"solverId": "solver-1",
"path": {
"intentId": "INT-2F8A3B1C",
"solverId": "solver-1",
"outputAmount": "1424500",
"surplus": "24500",
"estimatedFillTime": 3600,
"steps": [
{
"type": "fiat_gateway",
"asset": "KRW",
"amount": "1424500",
"provider": "fiat-gateway",
"currency": "KRW",
"beneficiaryAccount": "110-123-456789"
}
],
"score": 0,
"expiresAt": 1737123756789
}
}The response is returned directly — it is not wrapped in a
{ success, data }envelope.
| Field | Type | Description |
|---|---|---|
intentId | string | Echoes the submitted ID |
status | string | accepted |
planId | string | Settlement plan ID — DVP- + the first 8 chars of intentId, upper-cased |
solverId | string | The solver that accepted the Intent |
path | object | The chosen execution plan. See SolvedPath |
SolvedPath
| Field | Type | Description |
|---|---|---|
intentId | string | The Intent this path fulfils |
solverId | string | Solver that produced the path |
outputAmount | string | Final amount delivered to the recipient |
inputAmount | string | Input required for this path. For an exact-output intent this is the computed value (≤ input.limit); for exact-input it echoes the request. |
surplus | string | Exact-input: outputAmount − minOutputAmount. Exact-output: the input saved, input.limit − inputAmount. |
fees | array | Present when the intent references a firm quoteId. Itemized from the locked quote, with the same taxonomy as the receipt: [{ type, amountMinor, currency, exponent, collection }] (amounts follow the Money & Amounts convention), type = protocol_fee | payout_fee | network_fee. Quote-time values are the locked charge; the receipt reports what was actually charged — reconcile the two per type. FX spread is not a fee item (it is a component of the all-in rate). Kept separate from principal, not baked into the amounts above. |
estimatedFillTime | number | Estimated fill time in seconds (≈30 for crypto output, ≈3600 for fiat output) |
steps | array | Execution steps. See Fill steps |
score | number | Solver-internal score (lower is better) |
expiresAt | number | Path validity deadline (Unix ms; ≈ now + 5 min) |
Fill steps
Each path.steps[] entry is one of the following types: reserve, dex_swap, cex_otc, bridge, cow_match, fiat_gateway. Every step carries type, asset, amount, and optional chain; type-specific fields are added per kind.
The solver currently emits one step per Intent:
fiat_gateway— forfiatoutput:{ type, asset, amount, provider, currency, beneficiaryAccount }reserve— for crypto output:{ type, asset, amount, chain, source }(filled from the solver's own reserve)
Rejected Response
json
{
"intentId": "INT-2F8A3B1C",
"status": "rejected",
"reason": "no_liquidity"
}reason | Raised when |
|---|---|
beneficiary_required_for_remittance | outputRecipient ≠ user, but beneficiary.account / beneficiary.name is missing |
no_liquidity | No solver candidate can fill the requested pair |
below_minimum_output | The computed output is less than minOutputAmount / output.limit (exact-input intents) |
above_maximum_input | The input required to deliver the fixed output exceeds input.limit (exact-output intents) |
beneficiary_account_required_for_fiat_output | outputAsset.type is fiat, but no beneficiary account was provided |
Validation runs in the order above; the first failure is returned.
Infrastructure errors
Infrastructure failures (e.g. misconfiguration, or the settlement workflow failing to start) propagate as 5xx. When settlement startup fails after a balance reservation, the reservation is released before the error surfaces.
Status & lifecycle
This endpoint is terminal at submission time — it returns only accepted or rejected. After acceptance, settlement proceeds asynchronously through the DvP/HTLC workflow; track progress via the Status API (GET /executions/{reference}) and status webhooks. The authoritative on-chain state is tracked by the Intent settler contract: Created → Filled → Settled, or Cancelled.
Cancellation
There is no DELETE on this endpoint. Cancellation is initiated through other channels:
- FIX (institutional): an
OrderCancelRequest(35=F) cancels the intent, correlated byorigClOrdId+sessionId. - On-chain: an Intent can be cancelled permissionlessly once its fill deadline has passed.
Notes
- All amounts are decimal strings — no floating-point.
actorTypedefaults toinstitution;sidedefaults tosell.AssetDescriptorhas nodecimalsfield; token decimals are resolved internally.
