# Pay-In to Your Own Address

Deliver a user's bank transfers to a wallet they already own — MetaMask, Phantom, a hardware wallet — without an Aurea wallet.

## Overview

In the **standalone** mode the user brings their own address. Aurea never holds its key: the user proves once that they control the address by signing a message, and [Initiate Deposit](https://docs.aureahub.com/docs/payin-initiate.md) then accepts it as the destination of a virtual IBAN. The bank ramp converts each transfer to that IBAN and sends the crypto on-chain to the proven address.

- The user still onboards with the bank ramp (KYC), but needs no Aurea wallet — not even a primary EVM wallet.
- A proof belongs to one user, one environment and one kind of address. One EVM proof covers the address on every EVM network.
- With both modes on, a pay-in may go to one of the user's Aurea wallets or to a proven address.
- The mode decides where pay-ins go. Payouts work as in [Fiat Pay-Out](https://docs.aureahub.com/docs/guide-fiat-payout.md).

## Before You Start

- Aurea has switched on KYC, pay-in and the standalone mode for your tenant in the environment you use — [Bank Ramp Setup](https://docs.aureahub.com/docs/bank-setup.md). [Tenant Settings](https://docs.aureahub.com/docs/bank-settings.md) shows `modes.standalone` and the pairs offered for pay-in.
- The user has completed the bank ramp's onboarding, with KYC approved, in that environment — [Fiat Pay-In](https://docs.aureahub.com/docs/guide-fiat-payin.md), steps 1–3.
- Pick the pair first: its `addressFormat` — `evm` or `solana` — is the kind of address to prove. In production the pay-in pair today is `EURC` on `Solana`; in the sandbox there are Solana and EVM pairs ([Currencies & Networks](https://docs.aureahub.com/docs/bank-currencies.md)). The examples below are production.

## Step 1: Sign a Challenge

Ask for a challenge for the address, then have the wallet sign `message` exactly as returned. Signing moves no funds and costs no fee. The challenge can be verified for 10 minutes. Wallets that can't sign a message, and smart contract accounts, can't prove an address.

```typescript
const API = 'https://api.aureahub.com';
const headers = { Authorization: `Bearer ${token}`, 'Content-Type': 'application/json' };

async function post(path: string, body: object) {
  const res = await fetch(API + path, { method: 'POST', headers, body: JSON.stringify(body) });
  const data = await res.json();
  if (!res.ok) throw Object.assign(new Error(data.message), { status: res.status, details: data.details });
  return data;
}

// Solana, with @solana/wallet-adapter-react (Phantom, Solflare, ...)
import bs58 from 'bs58';

const { publicKey, signMessage } = useWallet();
const challenge = await post('/v1/ramp/bank/addresses/challenge', {
  family: 'solana',
  address: publicKey.toBase58(),
  isTestnet: false
});
// ed25519 over the message's UTF-8 bytes, sent in base58
const signature = bs58.encode(await signMessage(new TextEncoder().encode(challenge.message)));
```

An EVM wallet signs with EIP-191 `personal_sign`; with ethers v6:

```typescript
import { BrowserProvider } from 'ethers';

const signer = await new BrowserProvider(window.ethereum).getSigner();
const challenge = await post('/v1/ramp/bank/addresses/challenge', {
  family: 'evm',
  address: await signer.getAddress(),
  isTestnet: true // in the sandbox, e.g. for USDC_TEST on PolygonTestAmoy
});
const signature = await signer.signMessage(challenge.message);
```

## Step 2: Verify the Address

`201` the first time, `200` when the user already proved the address in that environment. A wrong signature tells you how many attempts are left; after the fifth, or once the challenge expired or was used, ask for a new challenge.

```typescript
const proven = await post('/v1/ramp/bank/addresses/verify', {
  challengeId: challenge.challengeId,
  signature,
  label: 'Phantom' // optional, up to 100 characters
});

// proven.id, proven.address (in the form Aurea stores it), proven.environment
```

## Step 3: Virtual IBAN

Send the proven address as `destinationAddress`, on a network of its kind and its environment. Always send it: without one, a Solana route falls back to the user's Aurea Solana wallet, not to a proven address. Any address the user has not proved there, or has revoked, answers `400` `NOAH_DESTINATION_NOT_ALLOWED`, and nothing is sent to the bank ramp.

```typescript
const iban = await fetch(API + '/v1/ramp/bank/payin/initiate', {
  method: 'POST',
  headers: { ...headers, 'Idempotency-Key': ibanRequestKey },
  body: JSON.stringify({ cryptoCurrency: 'EURC', network: 'Solana', destinationAddress: proven.address })
}).then(r => r.json());

// Show iban.iban, iban.bic, iban.accountHolderName, iban.bankName.
// Transfers to it arrive as EURC on the user's own Solana address.
```

Follow the transfers as in [Fiat Pay-In](https://docs.aureahub.com/docs/guide-fiat-payin.md), step 5.

## Step 4: Manage Addresses

[List Addresses](https://docs.aureahub.com/docs/bank-addresses-list.md) returns what the user proved in one environment; [Revoke an Address](https://docs.aureahub.com/docs/bank-address-revoke.md) stops new virtual IBANs to one. Both answer whatever your tenant's settings.

```typescript
const { addresses } = await fetch(API + '/v1/ramp/bank/addresses?isTestnet=false', { headers }).then(r => r.json());

await fetch(API + `/v1/ramp/bank/addresses/${addresses[0].id}`, { method: 'DELETE', headers }); // 204
```

> ⚠️ Revoking an address changes nothing at the bank ramp: a virtual IBAN created earlier for it keeps delivering there. Stop showing that IBAN to the user.

## Errors

- `403` `NOAH_MODE_OFF`: the standalone mode is off for your tenant in that environment — on the challenge, and on verifying a challenge of that environment.
- `400` `NOAH_ADDRESS_INVALID`: not an address of that kind, or an EVM address in mixed case with a wrong checksum.
- `409` `NOAH_ADDRESS_CHALLENGE_LIMIT`: the user has 5 open challenges; verify one or let it expire.
- `400` `NOAH_ADDRESS_SIGNATURE_INVALID`: signed with another key or over another text (`details.reason`, `details.attemptsLeft`).
- `400` `NOAH_ADDRESS_CHALLENGE_BURNT`, `400` `NOAH_ADDRESS_CHALLENGE_EXPIRED`, `409` `NOAH_ADDRESS_CHALLENGE_USED`: ask for a new challenge.
- `400` `NOAH_DESTINATION_NOT_ALLOWED` on Initiate Deposit: the address is not one the user proved in that environment, or it was revoked.
- `403` `NOAH_FUNCTION_OFF` and `400` `NOAH_PAIR_UNAVAILABLE`: as in [Initiate Deposit](https://docs.aureahub.com/docs/payin-initiate.md).

---

Web version: https://docs.aureahub.com/#guide-standalone-payin
