# Addresses

Crypto addresses appear in three different roles in the Exchange API. Each role has different ownership, lifecycle, and validation rules. Mixing them up is a common source of integration bugs.

This page describes the three roles and the API surface that supports each.

## The three roles

| Role | Direction | Owner | API surface |
|---|---|---|---|
| **Destination address** | On-ramp | End user (external wallet) | `destinationAddress` on `POST /v1/exchanges`; pre-validate via `POST /v1/addresses/validate` |
| **Deposit address** | Off-ramp | Spike (allocated from Spike's address pool) | `POST /v1/deposit-addresses`, `GET /v1/deposit-addresses/{id}`, `PATCH /v1/deposit-addresses/{id}`, `DELETE /v1/deposit-addresses/{id}` |
| **Refund address** | Off-ramp | End user (external wallet) | `refundAddress` on `POST /v1/deposit-addresses` (default for the user-visible deposit address), `PATCH /v1/deposit-addresses/{id}` (update or clear), and **required** on every `POST /v1/exchanges/refund` request body |

The roles never overlap. A destination address is never used for deposits, a deposit address is never used as a refund target, and a refund address is never used to receive an on-ramp delivery.

## Destination address (on-ramp)

The destination address is the end user's external wallet, where Spike will deliver the purchased crypto.

### Pre-validation

Always validate before debiting fiat. Call `POST /v1/addresses/validate` with `currency`, `network`, and `address` (and `tag` for memo-based networks). The response is a domain result, not a transport error: a `200` with `valid: false` means the address is not acceptable for the selected asset/network and the partner should ask the user to correct it. Treat `valid: true` as a precondition for the exchange create.

A successful response carries:

- `normalizedAddress` — the canonical form of the address. Use this for storage and display to avoid casing-drift bugs (EVM addresses are returned in EIP-55 mixed-case checksum form). For networks with a single canonical form (Bitcoin Bech32, Ripple r-addresses), this is the trimmed input unchanged.
- `tagRequired` — whether the network requires a destination tag/memo. Drive a tag input field in the UI when `true` (XRP, XLM, etc.).

### Validation reasons

When `valid` is `false`, `reason` is one of the following machine-readable values. The enum is open; treat unknown values as a generic invalid signal.

| Reason | Meaning |
|---|---|
| `INVALID_FORMAT` | Wrong general shape; usually a copy-paste mistake or a non-address string. |
| `INVALID_LENGTH` | Length outside the range expected by the network (truncated or padded). |
| `INVALID_PREFIX` | Address does not start with a recognised prefix (`bc1`, `0x`, `r`, …). Often indicates the user picked the wrong network. |
| `INVALID_ENCODING` | Encoding (Base58, Bech32, hex) could not be decoded. |
| `INVALID_CHECKSUM` | Embedded checksum does not match the computed one. Usually a single-character typo. |
| `NETWORK_MISMATCH` | Address is well-formed but belongs to a different network than the one declared. |
| `MISSING_TAG` | The network requires a destination tag/memo and none was supplied. |
| `INVALID_TAG` | A tag/memo was supplied but is not valid for the network. |
| `TESTNET_ADDRESS` | A valid testnet address; Spike accepts only mainnet addresses for partner flows. |

### Networks requiring a destination tag

XRP, XLM, and other memo-based networks require a destination tag in addition to the address. Pass `tag` in the `POST /v1/addresses/validate` request body. Omitting it returns `valid: false` with `reason: MISSING_TAG`. The success response's `tagRequired: true` is the cue to collect a tag from the user.

### Validator availability

`POST /v1/addresses/validate` may return `503` if the format-validator for that network is temporarily disabled. This is a transient operational state, not an "invalid address" signal — retry per `Retry-After`. Do not treat the 503 as a permanent rejection.

### Network selection

The destination address must be on a network supported by Spike for the target currency. Use `GET /v1/conversion-routes` to confirm the route is enabled and to see which network applies. The partner UI should make the network unambiguous to the user.

## Deposit address (off-ramp)

A deposit address is a blockchain address allocated by Spike where the end user will send crypto for conversion. It is the central object of the off-ramp flow in non-fixed-rate mode.

### Reusable in non-fixed-rate mode

A non-fixed-rate deposit address is **reusable**: it has no expiration, accepts multiple deposits over time, and creates a separate exchange for each compliant deposit it receives. Partner systems should not assume one address equals one transaction.

Each deposit produces an independent exchange with its own `exchangeId`. Reconcile at the deposit-transaction level using the blockchain transaction hash plus `exchangeId`.

### Lifecycle

```mermaid
flowchart LR
    A[POST /v1/deposit-addresses] --> B[ACTIVE: address available for deposits]
    B --> C{Deposit arrives}
    C --> D[Independent exchange created<br/>address remains ACTIVE]
    D --> B
    B --> E[DELETE /v1/deposit-addresses/{id}]
    E --> F[DISABLED: no new deposits accepted]
    F --> G[POST again creates a fresh ACTIVE address]
```

Key behaviors:

- A deposit address stays `ACTIVE` regardless of how many deposits it receives.
- `DELETE` disables the address. New deposits sent on-chain after disablement may still arrive at the address but will not be processed; partners must stop displaying the disabled address in their UI.
- After an address is disabled, the partner can create a fresh one for the same `(externalUserId, fromCurrency, toCurrency, network)` tuple.

### Idempotency

A repeat `POST /v1/deposit-addresses` with the same `(partnerId, externalUserId, fromCurrency, toCurrency, network)` tuple while an `ACTIVE` address exists returns the existing address — no new allocation. See [Idempotency](idempotency.md).

### Default refund address coupling

A deposit address can carry a default `refundAddress`. This default is **for partner-side ergonomics only** — when constructing a `POST /v1/exchanges/refund` request, the partner can copy this value into the request body. There is no server-side fallback: the refund API requires `refundAddress` to be present in every request body (see [Refunds](refunds.md)).

Set the default at creation via `POST /v1/deposit-addresses`, update it via `PATCH /v1/deposit-addresses/{id}` with a new `refundAddress`, or **clear** it by sending `refundAddress: null` on `PATCH`.

The recommended pattern is to set `refundAddress` at deposit-address creation so the partner system always has a default to reach for.

## Refund address (off-ramp)

The refund address is the end user's external wallet, where Spike will return crypto if a deposit cannot be converted.

### Address-network match

The refund address network must match the deposit network. A refund cannot be issued to an address on a different network than the deposited asset, even if the asset symbol is the same. Spike rejects mismatched refund requests with `code: REFUND_NETWORK_MISMATCH`.

### Where to set it

The refund API requires `refundAddress` on every `POST /v1/exchanges/refund` request. Two complementary patterns:

- **Per-refund body** (canonical): every refund request body carries `refundAddress`. This is the only place Spike reads the address from at refund time.
- **Default on the deposit address** (ergonomic): set `refundAddress` on the deposit address so the partner has a stored default to copy into the refund request body. This is partner-side state — it does not act as a server-side fallback.

### Validation timing

The refund address is validated at the moment it is set: on `POST /v1/deposit-addresses`, on `PATCH`, or on `POST /v1/exchanges/refund` when supplied in the request body. Validation enforces format, checksum, network compatibility, and (for memo-based networks) tag presence.

## Common questions

**Can I reuse an existing on-chain address I generated elsewhere as a Spike deposit address?**
No. Deposit addresses are allocated by Spike from its own pool. The partner cannot supply or override the deposit address.

**What if a user sends a deposit to a disabled address?**
Spike will not process the deposit through the normal off-ramp pipeline. Recovery requires Spike support and may not always be possible. The partner UI must stop displaying disabled addresses promptly.

**Can the refund address be the same as the destination address from a prior on-ramp?**
There is no technical restriction, as long as the network matches the deposit. From a product perspective, the partner controls how addresses are presented to the user.

**What happens if the user sends crypto on the wrong network to a deposit address?**
Recovery may require manual operations and is sometimes impossible. Make the asset and network unambiguous in the UI. See [Failure handling](../operations/failure-handling.md).
