> ## Documentation Index
> Fetch the complete documentation index at: https://docs.conduit.financial/llms.txt
> Use this file to discover all available pages before exploring further.

# Deposit-Funded Orders

> Create an order without naming a funding source and let Conduit hand you a crypto address to fund it at

## Overview

A **deposit-funded order** is an order you create without naming a `source`. Instead of pointing at a resource that already holds the funds, you tell Conduit which crypto asset the order will be funded in, and Conduit returns a crypto address to send that asset to. When the funds arrive and clear, the order executes on its own.

Use it when the money is not with Conduit yet — the end customer is about to send crypto in, and you want one call that both locks the rate and tells you exactly where the funds should go. Use a named `source` instead when the customer already holds a balance in a wallet or Virtual Account.

Deposit funding is **crypto-only**. An order funded from fiat always names its Virtual Account `source` explicitly.

## What the customer needs first

The funding address is Conduit's, not the customer's, so the customer needs **none** of the setup a wallet of their own would take:

* no `claim-non-custodial` call,
* no signer roster, passkey enrollment, or signing threshold,
* no wallet of their own on any chain — `GET /v2/customers/{customerId}/wallets` may be empty,
* no co-signature on the way out: the order executes on its own once the funds clear.

What the customer **does** need is the `crypto_wallet` feature active. Without it, `POST /v2/orders` returns `403 FEATURE_NOT_ENABLED` at the point Conduit goes to resolve the funding address — the order is never created. Submit the feature the usual way (`POST /v2/customers/{customerId}/features` with `type: "crypto_wallet"`) and wait for it to activate before the first order. See [Add a crypto wallet](/guides/add-crypto-wallet) for the feature request; stop after activation, and skip the claim.

<Note>
  This is the route for a customer who will never hold a Conduit wallet. The
  feature is the entitlement to move crypto, not an instruction to provision
  anything — activating it alone issues no address and asks nothing of your end
  user.
</Note>

## Which assets can fund an order

Deposit funding covers a fixed set of asset-and-chain pairs, because Conduit has to be able to send the funds back off the address it hands you:

| Asset | Chains                        |
| ----- | ----------------------------- |
| USDC  | `ethereum`, `base`, `polygon` |
| USDT  | `ethereum`, `polygon`, `tron` |
| ETH   | `ethereum`                    |

Any other combination is rejected at create with `400 UNSUPPORTED_ASSET` naming the pair you asked for. That includes pairs the API accepts elsewhere: `sourceAsset.code` and `sourceAsset.chain` are the API-wide asset and chain enums, so the schema will let you send `USDC` on `solana` and the create call is what refuses it. Name a `source` explicitly to move an asset outside this set.

<Warning>
  **`ETH` funds an order but cannot be attributed to a sender.** Conduit reads
  the sender off token transfers, and a native-coin transfer carries none — so
  an `ETH` transfer into a funding address has no sender to check against your
  registrations and no address to be returned to. It holds rather than funding
  or bouncing, and only Conduit can resolve it. Prefer a stablecoin pair for a
  flow you want to run unattended.
</Warning>

## Register the sending address first

A funding address accepts money **only from addresses the customer has registered**. Register the wallet the funds will be sent from — the registration has to exist by the time the funds land, so in practice you register once, up front, and reuse it across orders:

```http theme={null}
POST /v2/customers/{customerId}/wallets/registered-addresses
idempotency-key: <unique-key>

{
  "type": "self_custody",
  "chain": "ethereum",
  "address": "0x8f3a1e5b9c2d4a6f8e0b1c3d5f7a9b1c3d5e7f90",
  "selfCustodyAttestation": true
}
```

Registration is per customer, chain, and address: an address one customer registered does not clear funds for another. `201` means the address is registered and can send; `202` means screening has not resolved yet — see below, you do **not** have to wait for it. See [Registered addresses](/concepts/registered-addresses).

**Either custody type qualifies here.** `self_custody` and `third_party` both clear the funding gate — the gate asks only whether the address is registered for that customer and chain. (`purpose: intercompany` on a *payout* is the one place that additionally insists on `self_custody`; it has no bearing on funding.) Register a counterparty's wallet as `third_party` with its `originatorDetails`, and those details are what Conduit screens the sender against. A `self_custody` registration carries no originator, because the customer is both sides.

Funds from an address that has **resolved** to not-registered are sent straight back to where they came from, and there is no way to attach the sender afterwards — see [When a transfer is not accepted](#when-a-funding-transfer-is-not-accepted).

<Note>
  **Registering and sending straight away is the ordinary sequence — a `202` is
  not a reason to hold off.** A transfer that lands while its registration is
  still screening is **held, not returned**: it waits for the verdict and is
  credited to the order if the address clears. Only an address that has actually
  resolved to not-registered bounces a transfer. Given the 5-minute funding
  deadline below, waiting out a slow screen before you send is the more likely
  way to lose an order.
</Note>

A registration that screening later **suspends** is a different outcome again: the address is registered, so the transfer is not bounced as unregistered — it fails the compliance check instead. The funds are not sent straight back either. They park for review, and the review decides between returning them to the sending address (the transfer ends `returned`) and holding them in Conduit's custody (it ends `frozen`).

<Warning>
  Deposit funding is unavailable from any wallet you cannot register at all — an
  exchange withdrawal, an OTC desk, an omnibus custodian — because those send
  from addresses you do not control or cannot predict. Fund those flows from a
  Virtual Account you name as the order's `source`, or have the customer hold a
  Conduit wallet and name that: a wallet deposit accepts an unregistered sender
  and collects the sender's identity afterwards, which a funding address never
  does. See [Receive crypto](/guides/receive-crypto-lifecycle).
</Warning>

## Creating one

Omit `source` and send `sourceAsset` — the asset `code` plus its `chain`. Exactly one of the two is required; sending both, or neither, is a `400`.

**Both fields are required.** `sourceAsset` is always crypto, so omitting `chain` is a `400 VALIDATION_ERROR` on `/sourceAsset/chain` — there is no default chain for an asset code, and none is inferred. `USDC` alone does not mean `USDC` on Ethereum.

```http theme={null}
POST /v2/orders
idempotency-key: <unique-key>

{
  "sourceAsset": { "code": "USDC", "chain": "ethereum" },
  "destination": { "type": "virtual_account", "id": "vac_..." },
  "lockSide": "source",
  "amount": "100.00"
}
```

Everything else is unchanged: `destination`, `lockSide`, `amount`, and an optional `autoPayout` behave exactly as they do on an order with a named source.

<Warning>
  Do **not** send `autoExecute`. A deposit-funded order always executes
  automatically once its deposit clears — there is no other way to fund it — so
  sending the field at all (either value) is rejected with a `400` on
  `/autoExecute`. The order still reports `autoExecute: true` in its response.
</Warning>

## The funding address

Every read of the order — the create response, `GET /v2/orders`, `GET /v2/orders/:id`, and the `order.created` webhook — carries a `depositInstructions` array. It is present **only** on deposit-funded orders, and it is always exactly one block:

```json theme={null}
{
  "id": "ord_...",
  "status": "pending",
  "depositInstructions": [
    {
      "type": "crypto_address",
      "address": "0x742d35cc6634c0532925a3b844bc9e7595f2bd18",
      "chain": "ethereum",
      "asset": "USDC",
      "expiresAt": "2026-01-16T09:30:00.000Z"
    }
  ]
}
```

This is the same array-of-blocks shape a Virtual Account uses for `depositInstructions`, so funding instructions read the same way whatever you are funding. Send `asset` to `address` on `chain` before `expiresAt`.

**How much to send:** the order's `totalDebit`, not `sourceAsset.amount`. `totalDebit` is what the customer pays on the source side — the amount being converted plus any fee charged in the source asset — and the order executes only once the funds cover it. Read it off the same response that gave you the address.

<Warning>
  Read the address off **each order**. Conduit owns the account behind it and
  may replace it at any time without notice, so an address cached against a
  customer can go stale. There is no endpoint that resolves a customer to their
  funding address — the order is the only place it is published.
</Warning>

## `source` is absent, by design

A deposit-funded order **omits `source` entirely** from every response — `POST /v2/orders`, `GET /v2/orders`, and `GET /v2/orders/:id` alike. The account behind the funding address is Conduit infrastructure, not a customer resource: `GET /v2/customers/:customerId/wallets` does not list it, and `GET /v2/customers/:customerId/wallets/:walletId` returns `404` for it. There is no id to hand you, so none is sent.

The presence of `depositInstructions` is the discriminator. Branch on that, not on `source`.

## The funding deadline

`lockExpiresAt` on a deposit-funded order is a **funding deadline**, not a rate expiry, and `depositInstructions[0].expiresAt` always equals it.

<Warning>
  **The deadline is 5 minutes.** It used to be 24 hours. Read `lockExpiresAt`
  off the order rather than assuming a window: a test that creates an order,
  does something else, then funds it will now find the order already
  `cancelled (expired)` and the funds on their way back.
</Warning>

| If…                                                   | Then…                                                                                  |
| ----------------------------------------------------- | -------------------------------------------------------------------------------------- |
| Funds covering `totalDebit` clear before the deadline | The order executes automatically and reaches `succeeded`.                              |
| Nothing arrives by the deadline                       | The order is cancelled: `order.cancelled` with `reason: "expired"`.                    |
| Less than `totalDebit` arrives                        | The order never reaches its total, so it expires like an unfunded one at the deadline. |
| More than `totalDebit` arrives                        | The order executes; the surplus is not credited (see below).                           |

You can still cancel a pending deposit-funded order yourself with `POST /v2/orders/:id/cancel`.

## Funds no order claims are sent back

Crypto that reaches a funding address without a matching order to claim it is **not** credited to the customer and cannot be spent. That covers every near miss: an amount that does not cover an order, funds arriving after the order expired or was cancelled, the surplus from an over-funded order, and funds sent to an address with no order behind it at all.

Once no pending order is left on the address, Conduit sends those funds back on-chain to the address they came from. There is no separate waiting period: the order's funding deadline is the only clock. A remainder worth under a dollar stays put rather than being sent back for less than it costs to move.

<Warning>
  Reconcile against the **order** — its status, and `depositInstructions` —
  never against the deposit. If an order reached `cancelled (expired)`, treat
  any funds sent to its address as on their way back to the sender.
</Warning>

Two consequences worth designing for:

* **A short send leaves the order pending, not part-filled.** The order executes only once the address holds at least `totalDebit`; until then it sits `pending` and the clock keeps running. If the shortfall is never made up before the deadline, the order expires and the funds go back.
* **Re-funding needs a new order.** Once an order is terminal its address no longer claims anything. Create a fresh order and read the new `depositInstructions`.

One case produces no return at all: funds whose on-chain sender Conduit could not determine have nowhere to go back to, so nothing is dispatched. Those stay on the funding address until Conduit resolves the sender and sends them back by hand.

## Reading the deposit's own record

A deposit into a funding address is an ordinary transaction, readable and listable on `GET /v2/transactions` exactly like any other deposit. When it has not `failed`, it carries three extra fields:

| Field       | Meaning                                                                                                                                                                                                                                                                                                                                        |
| ----------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `funded`    | The orders holding a claim on this transfer, oldest first, each as `{ orderId, orderStatus, amount }`. An order appears while it is `pending`, and stays for good once it has drawn the funds — including when it `failed` afterwards. An order that fails or is cancelled before it draws anything releases its claim and drops off the list. |
| `returned`  | How much of this transfer has gone back to the sender.                                                                                                                                                                                                                                                                                         |
| `available` | How much of this transfer no order claims and nothing has sent back — still there to fund your next order.                                                                                                                                                                                                                                     |

A `pending` order's entry in `funded` is earmarked for it; an order that has drawn the funds has actually moved them off the address, which can be well before it reports `succeeded`. That is why `orderStatus` rides on the field at all: an order that drew the funds and then failed keeps its entry and reports `failed`, because that money has left and does not come back. Only a claim that never drew anything is released when its order fails or is cancelled — that slice re-attributes to the next order or goes back.

**On a deposit that has not failed, the three fields always add up.** `source.assetAmount` equals the sum of every `funded[].amount` plus `returned` plus `available`. The transfer is filled in the order the funds left it — `returned` first, then each claim in `funded` oldest first, and whatever is left is `available` — so no two of the three ever count the same money. Use this to answer "did my money stick?" without a second request. A `failed` deposit omits all three — see below for where to look instead. A transfer into a funding address is never charged a deposit fee, so its `source.assetAmount` and `destination.assetAmount` are equal and the sum reconciles against either; a fee-bearing deposit is a bank transfer into a Virtual Account, which carries none of these three fields.

```json theme={null}
{
  "id": "txn_034A1PiBrGsEgzme1VHQsP",
  "type": "deposit",
  "status": "completed",
  "source": {
    "type": "external_crypto",
    "assetAmount": { "code": "USDC", "chain": "ethereum", "amount": "1200.000000" }
  },
  "funded": [
    { "orderId": "ord_034A0gCCW3DbnAZtATH3rA", "orderStatus": "succeeded", "amount": { "code": "USDC", "chain": "ethereum", "amount": "1000.000000" } }
  ],
  "returned": { "code": "USDC", "chain": "ethereum", "amount": "200.000000" },
  "available": { "code": "USDC", "chain": "ethereum", "amount": "0.000000" }
}
```

An order carries the reverse view: `fundedBy`, an array of `{ transactionId, amount }` naming every deposit that funded it. Read it off `GET /v2/orders/:id`.

## The return is its own transaction

Money sent back from a funding address is a second transaction, `type: "deposit_return"`, with its own `id`, `status`, and on-chain hash. It carries `returnOf`, the id of the deposit it returns:

```json theme={null}
{
  "id": "txn_034A1PiBqxWhdd9FzcAcfm",
  "type": "deposit_return",
  "status": "completed",
  "source": {
    "type": "deposit_address",
    "assetAmount": { "code": "USDC", "chain": "ethereum", "amount": "200.000000" }
  },
  "destination": {
    "type": "external_crypto",
    "txHash": "0xOUTBOUND",
    "assetAmount": { "code": "USDC", "chain": "ethereum", "amount": "200.000000" }
  },
  "returnOf": "txn_034A1PiBrGsEgzme1VHQsP"
}
```

The `deposit_return` transaction itself always looks the same regardless of how much it returns. What it means for the deposit side varies:

* **A transfer that funded something first, then had a remainder returned** — a partial return, or a full return of an over-funded transfer after its order settled — leaves the deposit `completed`, with `returned` and `available` reflecting exactly what happened.
* **A transfer that never funded anything** — nothing ever claimed it, or it could not be accepted at all — leaves the deposit `failed`, with `funded`, `returned` and `available` all omitted. The `deposit_return` transaction is still there, and its `returnOf` still names the deposit: check it to see where the money went.

`deposit_return` carries no `linkedOrderId`: it is returned precisely because no order claimed it, and every order that did claim funds reports its own outcome on `order.*`.

## When a funding transfer is not accepted

A transfer that is not accepted never funds anything, so the order cannot reach its total and expires at its deadline. The deposit itself ends in `status: "failed"` with a neutral reason and no `failureCode` — and, on a failed deposit, `funded`, `returned` and `available` are all omitted. Check `GET /v2/transactions?type=deposit_return` for the matching `returnOf` to see where the money went; no second transaction is produced when nothing was sent back. Contact support if it persists.

### There is no sender-information path here

A crypto deposit into a customer's **own wallet** that arrives from a sender Conduit has not seen parks on `transaction.awaiting_sender_information` and waits — you clear it by submitting the sender's details, and the funds are credited. **None of that applies to a funding address.** The registration check is the whole gate:

|                                                 | Deposit into the customer's wallet                          | Deposit into an order's funding address    |
| ----------------------------------------------- | ----------------------------------------------------------- | ------------------------------------------ |
| Unregistered sender                             | Parks, then credits once you submit the sender              | Returned to the sender                     |
| `transaction.awaiting_sender_information`       | Fires, carrying a deadline                                  | Never fires                                |
| `POST /v2/transactions/{id}/sender-information` | Clears the deposit; `register: true` also saves the address | `404` — refused before it records anything |
| Deadline to act                                 | 30 days                                                     | None; there is nothing to act on           |

So the fix for an unregistered sender is always *before* the fact: register the address, then send. After the funds have bounced, a new transfer from a registered address is the only way forward — and since the order will have expired by then, a new order too.

Sending `originator` on the sandbox funding route is refused with `400 VALIDATION_ERROR` for the same reason: accepting sender identity there would let an unregistered sender fund an order. See [Receive crypto](/guides/receive-crypto-lifecycle) for the wallet-deposit path this contrasts with.

## Filtering transactions at a funding address

`GET /v2/transactions` accepts `sourceAddress` — an exact match on the address that sent a transfer.

To find every deposit that funded a given order, read `fundedBy` off that order instead (`GET /v2/orders/:id`).

## The funding address on transactions

When a client-visible transaction has the funding address on one of its sides, that side is a `deposit_address` block rather than the usual `wallet` block:

```json theme={null}
{
  "type": "deposit_address",
  "address": "0x742d35cc6634c0532925a3b844bc9e7595f2bd18",
  "assetAmount": { "code": "USDC", "chain": "ethereum", "amount": "100.000000" }
}
```

It carries no `walletId` — there is no wallet resource to look up. Treat it as its own case in your side handling; the chain travels with the asset inside `assetAmount`.

## Errors

These are the codes specific to funding an order this way; `POST /v2/orders` can return any of its usual pricing and validation errors besides.

| Code                                       | Status | Cause                                                                                                                                            |
| ------------------------------------------ | ------ | ------------------------------------------------------------------------------------------------------------------------------------------------ |
| `VALIDATION_ERROR` on `/source`            | 400    | Both `source` and `sourceAsset` were sent, or neither was.                                                                                       |
| `VALIDATION_ERROR` on `/sourceAsset`       | 400    | `sourceAsset` named a fiat asset. Deposit funding is crypto-only.                                                                                |
| `VALIDATION_ERROR` on `/sourceAsset/chain` | 400    | `chain` was omitted. It is required on a crypto asset reference.                                                                                 |
| `VALIDATION_ERROR` on `/autoExecute`       | 400    | `autoExecute` was sent. Omit it.                                                                                                                 |
| `UNSUPPORTED_ASSET`                        | 400    | That asset and chain pair cannot fund an order — see [Which assets can fund an order](#which-assets-can-fund-an-order). Name a `source` instead. |
| `FEATURE_NOT_ENABLED`                      | 403    | The customer does not have the `crypto_wallet` feature active. Nothing was created; activate it and retry.                                       |
| `DEPOSIT_ADDRESS_UNAVAILABLE`              | 409    | A funding address for this customer is still being provisioned — two orders raced on the customer's first order on a chain. Retry shortly.       |

## Related

<CardGroup cols={2}>
  <Card title="Convert crypto" href="/guides/convert">
    Wallet-to-wallet conversions, including the deposit-funded variant.
  </Card>

  <Card title="Money Movement Lifecycle" href="/guides/money-movement-lifecycle">
    Where an order sits in the journey of a balance.
  </Card>

  <Card title="OFFRAMP orders in sandbox" href="/sandbox/offramps">
    Drive a deposit-funded order end to end with simulated funds.
  </Card>

  <Card title="Money" href="/concepts/money">
    The amount shape every field on an order uses.
  </Card>
</CardGroup>
