> For the complete documentation index, see [llms.txt](https://sodex.com/documentation/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://sodex.com/documentation/for-developers/developers/mirror-protocol/generate-address.md).

# Generate Custody Address

A custody deposit address is assigned to one SoDEX user (an EVM account) on one external `chainName`. It is the destination for a custody deposit, not a bridge contract. Create or query it through the [Deposit Addresses API](/documentation/for-developers/api-reference/mirror-api/deposit-addresses.md), then confirm the same value is stored on ValueChain before displaying it or sending funds.

## Create or query

Each creation request consumes **200 REST weight** from the IP budget. Use `GET` for polling. See [Address creation rate limit](/documentation/for-developers/api-reference/mirror-api/deposit-addresses.md#address-creation-rate-limit).

`chain` must be the exact `chainName` from [Asset Configuration](/documentation/for-developers/api-reference/mirror-api/asset-configuration.md) for a route with `custody.allowDeposit` equal to `true`. The path `{userAddress}` is the receiving user's EVM address on ValueChain.

Query an existing assignment:

```bash
curl -sS "https://mainnet-gw.sodex.dev/api/v1/user/YOUR_USER_ADDRESS/deposit-address?chain=TON" \
  -H "Accept: application/json"
```

If `address` and `status` are empty, request creation for that chain:

```bash
curl -sS -X POST "https://mainnet-gw.sodex.dev/api/v1/user/YOUR_USER_ADDRESS/deposit-address" \
  -H "Content-Type: application/json" \
  --data '{"chain":"TON"}'
```

To start creation for every custody chain available to the user:

```bash
curl -sS -X POST "https://mainnet-gw.sodex.dev/api/v1/user/YOUR_USER_ADDRESS/deposit-addresses" \
  -H "Content-Type: application/json"
```

Poll `GET` until `status` is terminal. Creation is asynchronous; an empty or `Processing` address is not a deposit destination.

| `status`     | Meaning                                            |
| ------------ | -------------------------------------------------- |
| Empty        | No assignment yet. `POST` to create, then poll.    |
| `Processing` | Creation in progress. Keep polling.                |
| `Enabled`    | API has an address. Verify it on chain before use. |
| `Suspicious` | Do not display or send to this address.            |

`POST` does not transfer funds and does not require a private key. A poll timeout is not permission to send.

## Verify on ValueChain

The API can return `Enabled` before you treat the destination as authoritative. Read [SoDexTokenCustody](/documentation/for-developers/developers/mirror-protocol/contracts.md) on ValueChain Mainnet (`0xB37295d1ea21E65b76dCB2E85e035eE0dBFC70CB`) and require the API `address` to appear in the on-chain list for the same account and chain.

```solidity
function getDepositWallet(address account, string memory chain) external view returns (string memory depositWallet);

function getDepositWalletList(address account, string memory chain) external view returns (string[] memory);
```

* `account` is the same EVM address used in the API path.
* `chain` is the same `chainName` string used in the API (`TON`, not a chain ID).
* `getDepositWalletList` is the list to check against. If the list is empty but a legacy single wallet exists, the contract returns that wallet as a one-element list.
* `getDepositWallet` is the legacy single mapping. Do not use it alone when the list can contain more than one destination.

The API address is correct only if it is an exact string in `getDepositWalletList`. Custody destinations are often non-EVM (for example TON); do not checksum or rewrite the string. An empty list means this account and chain are not stored on chain yet — keep polling the API and the contract; do not send.

```javascript
import { createPublicClient, http, parseAbi } from "viem";

const USER = "YOUR_USER_ADDRESS";
const CHAIN = "TON";
const API_ADDRESS = "ADDRESS_FROM_ENABLED_API_RESPONSE";

const custody = "0xB37295d1ea21E65b76dCB2E85e035eE0dBFC70CB";
const abi = parseAbi([
  "function getDepositWallet(address account, string chain) view returns (string)",
  "function getDepositWalletList(address account, string chain) view returns (string[])",
]);

const client = createPublicClient({
  transport: http("https://mainnet.valuechain.xyz"),
});

const list = await client.readContract({
  address: custody,
  abi,
  functionName: "getDepositWalletList",
  args: [USER, CHAIN],
});

if (!list.includes(API_ADDRESS)) {
  throw new Error("API custody address is not on chain for this account and chain");
}
```

Only after this check should the address be shown or used as a deposit destination. Then follow [Custody Deposits](/documentation/for-developers/developers/mirror-protocol/lifecycle/custody-deposits.md) to transfer from the external network.
