2. Core Concepts
Read this before integrating. These concepts appear throughout the API.
Payment intent
A payment intent is the central object. It represents "I expect a payment to
this address." When you create one (POST /intents), we:
- Derive a unique deposit address for it — never reused across intents.
- Give it a stable
derivation_indexyou use to track it. - Persist it so detection and settlement survive restarts.
An intent records:
| Field | Meaning |
|---|---|
derivation_index | The stable integer id. Use this to poll status and reference the payment. |
address | The Solana deposit address to show the payer. |
mint | The asset accepted: null = native SOL, else an SPL mint address. |
mint_decimals | Decimals of the token (null for SOL). |
expected_lamports | Expected amount in base units (see below). null = accept any amount. |
received_lamports | Amount received so far, in base units. |
status | Lifecycle state (see below). |
reference | Your own id (order id, user id, memo). Optional, opaque to us. |
expires_at | When the payment window closes. Optional. |
Naming note: the fields are called
expected_lamports/received_lamportsfor historical reasons, but they hold base units of the intent's asset — lamports for SOL, token base units for an SPL token. Treat them as generic integer amounts.
Assets: SOL or any SPL token
Each intent accepts exactly one asset, chosen at creation:
- No
mint→ native SOL. Amounts are in lamports (1 SOL = 1,000,000,000 lamports). mintset → that SPL token. We fetch the mint's decimals on-chain (which also validates it exists), and amounts are in the token's base units.
Base units = the smallest indivisible unit. To convert:
base_units = ui_amount × 10^decimals
ui_amount = base_units ÷ 10^decimals
Examples:
- 0.5 SOL (9 decimals) →
500000000lamports. - 100 USDC (6 decimals) →
100000000base units. - 100 units of a 9-decimal token →
100000000000base units.
Both classic SPL Token and Token-2022 mints are supported automatically.
The settlement lifecycle
Once a payment arrives, we settle it automatically: sweep the funds to your
sweep destination, refund any excess, and so on. You don't trigger this — you
just observe the status.
Status values
status (and the derived action in /status, which is a friendlier alias):
| status | action | Meaning |
|---|---|---|
pending | waiting | Created, no payment detected yet. |
underpaid | underpaid | Received less than expected. Waiting for the rest. |
paid | paid | Received the expected amount (or any, for open-ended intents). About to sweep. |
overpaid | overpaid | Received more than expected. Will sweep expected + refund excess. |
sweeping / settling | paid | Settlement transaction in progress. |
swept | swept | Terminal success. Funds are in your destination wallet. |
refunding | refunding | Being refunded (expired with a partial balance). |
refunded | refunded | Terminal. Funds returned to the sender. |
expired | expired | Terminal. Window closed with nothing received. |
refund_failed / settle_failed | refund_failed | Terminal-ish. Automatic settlement/refund exhausted retries; contact support. |
The status you care about most is swept — that means the money is yours and
settlement tells you exactly how much.
Normal happy path
pending → paid → sweeping → swept
Overpayment
pending → overpaid → settling → swept
(expected amount → your wallet; excess → back to sender)
Underpayment then completion
pending → underpaid → (sender sends the rest) → paid → swept
Underpayment then expiry (SOL)
pending → underpaid → (expires) → refunding → refunded
(the partial amount is returned to the sender)
What "settled" means and the settlement object
When an intent reaches swept, /status includes a settlement object — the
exact on-chain amounts that were delivered. This is your source of truth for
accounting:
"settlement": {
"asset": "SOL",
"decimals": 9,
"destination_amount": 499995000,
"destination_ui": 0.499995,
"platform_fee_amount": 0,
"platform_fee_ui": 0.0,
"signatures": ["4bd..."]
}
destination_amount— base units that reached your destination wallet (net of platform fee, and for overpayments net of the refunded excess).platform_fee_amount— base units sent to your platform-fee wallet (0 if off).asset/decimals— so you can format correctly ("SOL"or the mint).signatures— the on-chain sweep transaction signature(s) for verification.
destination_amountcan be less thanreceived_lamports— that's expected.received_lamportsis the gross amount the payer sent;destination_amountis what you actually net after fees/refunds. Usesettlementfor accounting.
Fees, refunds, and edge cases (summary)
We handle these automatically:
- Network fee. Every sweep pays the tiny Solana network fee (~5000 lamports). For SOL it comes out of the swept amount; for tokens we front it, so you receive the full token amount.
- Platform fee (optional). A % of each sweep is diverted to your platform wallet in the same transaction (no extra network fee). Configured per account in Settings.
- Overpayment. SOL: the expected amount is swept, the excess is refunded to the sender. Token: the full balance is swept to you (no token excess-refund).
- Underpayment + expiry. SOL: the partial amount is refunded to the sender. Token: held for manual handling.
- Wrong asset. A token sent to an intent expecting a different asset is
automatically refunded to the sender on an independent track (reported in
token_refunds). SOL sent to a token intent is held for manual review.
Idempotency & safety guarantees
- Detection is idempotent. The same on-chain transaction is never counted twice.
- Settlement is race-safe and crash-safe. A payment is never swept twice, and a settlement interrupted mid-flight is retried automatically.
- Retries with backoff. Transient failures retry, then surface as a
*_failedstatus for attention — never silently lost.
Next: API reference →