# Verify an Address

Send the signed challenge: a valid signature proves the address, and pay-ins can be delivered to it.

## Overview

The second step after [Ask for a Challenge](https://docs.aureahub.com/docs/bank-address-challenge.md). Aurea checks the signature against the challenge's message and address, then keeps the address for the user in the challenge's environment.

- **The signature.** EVM: the 65-byte signature as `0x` and 130 hex characters, as `personal_sign` returns it. Solana: the 64-byte signature in base58.
- `label` is optional: up to 100 characters, trimmed, for the user to recognise the address.
- `201` with the proven address. `200` when the user already holds that address in that environment — proven with an earlier challenge, or this signed challenge sent again: the address is returned as it was first proven, label included.
- **A wrong signature** answers `400` `NOAH_ADDRESS_SIGNATURE_INVALID`, with `details.reason` — `wrong_signer` when it reads but another key made it or it signs another text, `malformed` when it cannot be read — and `details.attemptsLeft`. The fifth wrong signature burns the challenge: `400` `NOAH_ADDRESS_CHALLENGE_BURNT`.
- After `expiresAt` the challenge answers `400` `NOAH_ADDRESS_CHALLENGE_EXPIRED`, whatever the signature. A challenge already used or burnt answers `409` `NOAH_ADDRESS_CHALLENGE_USED`, unless its valid signature is sent again while the user still holds the address (`200`). In each of these cases, ask for a new challenge.
- A challenge that is not the user's answers `404`, before your tenant's settings are read.
- **The standalone mode must be switched on** for your tenant in the challenge's environment, otherwise `403` `NOAH_MODE_OFF`, and the challenge is left as it was.
- Two users of your tenant may prove the same address: each of them holds its key. Aurea records every proof in your tenant's activity log, without the signature.
- Answers `403` to a token without a tenant, and when your tenant does not use the bank ramp.

## Endpoint

### `POST /v1/ramp/bank/addresses/verify`

Authentication: bearer token required.

Checks the signature of one of the user's challenges and keeps the address it proves.

**Request body**

| Name | Type | Required | Description |
| --- | --- | --- | --- |
| `challengeId` | string | yes | The `challengeId` of the challenge, a UUID |
| `signature` | string | yes | The signature of the challenge's message, up to 200 characters: hex for EVM, base58 for Solana |
| `label` | string | no | A name for the address, up to 100 characters |

**Responses**

`201` Proven

```json
{
  "id": "7a1d3c5e-9b2f-4e6a-8c0d-1f3b5d7e9a2c",
  "family": "evm",
  "address": "0xC0207704CaEB9342cf491Ad4a177F43592df65a7",
  "label": "Hardware wallet",
  "proof": "eip191",
  "environment": "sandbox",
  "verifiedAt": "2026-09-16T10:02:41.000Z"
}
```

`200` Already proven

```json
{
  "id": "7a1d3c5e-9b2f-4e6a-8c0d-1f3b5d7e9a2c",
  "family": "evm",
  "address": "0xC0207704CaEB9342cf491Ad4a177F43592df65a7",
  "label": "Hardware wallet",
  "proof": "eip191",
  "environment": "sandbox",
  "verifiedAt": "2026-09-16T10:02:41.000Z"
}
```

`400` Wrong signature

```json
{
  "statusCode": 400,
  "error": "BadRequestError",
  "message": "The signature was not made with this address's key over the challenge's message.",
  "details": { "code": "NOAH_ADDRESS_SIGNATURE_INVALID", "reason": "wrong_signer", "attemptsLeft": 4 }
}
```

`400` Burnt

```json
{
  "statusCode": 400,
  "error": "BadRequestError",
  "message": "Too many wrong signatures: this challenge can no longer be used. Ask for a new one.",
  "details": { "code": "NOAH_ADDRESS_CHALLENGE_BURNT" }
}
```

`400` Expired

```json
{
  "statusCode": 400,
  "error": "BadRequestError",
  "message": "This challenge has expired. Ask for a new one.",
  "details": { "code": "NOAH_ADDRESS_CHALLENGE_EXPIRED" }
}
```

`409` Used

```json
{
  "statusCode": 409,
  "error": "ConflictError",
  "message": "This challenge was already used. Ask for a new one.",
  "details": { "code": "NOAH_ADDRESS_CHALLENGE_USED" }
}
```

`403` Standalone off

```json
{
  "statusCode": 403,
  "error": "ForbiddenError",
  "message": "The bank ramp's standalone mode is not switched on for this tenant in sandbox.",
  "details": { "code": "NOAH_MODE_OFF", "mode": "standalone", "environment": "sandbox" }
}
```

`404` Not the user's

```json
{ "statusCode": 404, "error": "NotFoundError", "message": "Address challenge not found" }
```

A malformed signature answers the same `400` with `"reason": "malformed"` and the message `The signature cannot be read: an EVM address signs with a 65-byte hex signature, a Solana address with a 64-byte base58 one.`

## Implementation

```javascript
async function verifyAddress(token, { challengeId, signature }, label) {
  const res = await fetch('https://api.aureahub.com/v1/ramp/bank/addresses/verify', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${token}` },
    body: JSON.stringify({ challengeId, signature, label })
  });
  const data = await res.json();

  if (res.ok) return data; // 201 proven, 200 already proven: data.id, data.address

  switch (data.details?.code) {
    case 'NOAH_ADDRESS_SIGNATURE_INVALID':
      throw new Error(`Signed with another key: ${data.details.attemptsLeft} attempts left`);
    case 'NOAH_ADDRESS_CHALLENGE_BURNT':
    case 'NOAH_ADDRESS_CHALLENGE_EXPIRED':
    case 'NOAH_ADDRESS_CHALLENGE_USED':
      throw new Error('Ask for a new challenge and sign it again');
    default:
      throw new Error(`${res.status}: ${data.message}`);
  }
}
```

---

Web version: https://docs.aureahub.com/#bank-address-verify
