> ## Documentation Index
> Fetch the complete documentation index at: https://sdk-guide.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Address Book

> Save, look up, and whitelist recipient addresses with memo support

## Overview

The address book is a per-user list of saved recipient addresses. Each entry stores an address, its chain type, a unique name, a `CEX` / `Wallet` classification, and an optional memo (destination tag).

It has two jobs:

> **Convenience**
>
> A named, reusable list of recipients you can filter by chain type or address type.

> **Whitelisting**
>
> When forced-address-book enforcement is on, a transfer whose destination is not in the book is rejected before it is signed.

> [!NOTE]
>
> Entries are not subject to a confirmation step. A personal entry is stored with
> `is_verified = true` and counts for whitelisting the moment `createAddressBookEntry` resolves.
> The OTP and pending-verification flow exists only in the organization address book. See
> [How the organization address book differs](#how-the-organization-address-book-differs).

## Types

```typescript theme={null}
export enum EAddressBookEntryType {
  CEX = "CEX",
  Wallet = "Wallet",
}

export type AddressBookEntryType = "CEX" | "Wallet";

export interface AddressBookEntry {
  id: string;
  name: string;
  address: string;
  chainType: ChainType;
  type: AddressBookEntryType;
  memo: string | null;
  createdAt: string;
}

export interface AddressTypeResponse {
  type: AddressBookEntryType | null;
}
```

`EAddressBookEntryType` is the enum form of the same two values, and `AddressBookEntryType` is the string-literal union the method inputs accept. Either works: `EAddressBookEntryType.CEX` and `'CEX'` are the same value.

> [!NOTE]
>
> The HTTP response for an entry also carries `updatedAt`, and the response for an address-type
> lookup also carries `isInAddressBook` and `isOnlyExist`. Those fields are present on the wire but
> are not declared on the SDK's `AddressBookEntry` / `AddressTypeResponse` interfaces. See
> [Address type lookup](#address-type-lookup) for how to read them.

### Method inputs

```typescript theme={null}
export interface CreateAddressBookEntryInput {
  name: string;
  address: string;
  chainType: EChainType;
  type: AddressBookEntryType;
  memo?: string;
}

export interface GetAddressBookEntriesInput {
  chainType?: EChainType;
  type?: AddressBookEntryType;
}

export interface UpdateAddressBookEntryInput {
  id: string;
  name?: string;
  memo?: string | null;
}

export interface DeleteAddressBookEntryInput {
  id: string;
}

export interface GetAddressTypeInput {
  chainType: EChainType;
  address: string;
}
```

## Methods

```typescript theme={null}
createAddressBookEntry(input: CreateAddressBookEntryInput): Promise<AddressBookEntry>
getAddressBookEntries(input?: GetAddressBookEntriesInput): Promise<AddressBookEntry[]>
updateAddressBookEntry(input: UpdateAddressBookEntryInput): Promise<AddressBookEntry>
deleteAddressBookEntry(input: DeleteAddressBookEntryInput): Promise<void>
getAddressType(input: GetAddressTypeInput): Promise<AddressTypeResponse>
```

None of these require an unlocked wallet session. They are plain API-key calls and touch no key material.

> [!NOTE]
>
> The API key must carry one of `wallet_management`, `vault_management` or
> `transaction_management`. Create, update and delete additionally pass through the
> force-2FA guard; list and lookup do not.

## Creating entries

```typescript theme={null}
import { EChainType, EAddressBookEntryType } from 'vaults-multichain-sdk';

const entry = await sdk.createAddressBookEntry({
  name: 'Alice Wallet',
  address: '0x5b927c461b71Ce25a74878dBe504492Ab80cbeE4',
  chainType: EChainType.EVM,
  type: EAddressBookEntryType.Wallet,
});

console.log(entry.id, entry.name, entry.createdAt);
```

The address is validated twice. The SDK runs `assertValidAddressForChain` before the request leaves your process, and the server re-validates against the chain type with its own network mode, so a mainnet deployment rejects a testnet-only address form. A client-side failure throws before any HTTP call is made.

### Entries with a memo

```typescript theme={null}
const xrpEntry = await sdk.createAddressBookEntry({
  name: 'Exchange XRP deposit',
  address: 'rN7n7otQDd6FczFgLdlqtyMVrRP3Jtxys',
  chainType: EChainType.XRP,
  type: EAddressBookEntryType.CEX,
  memo: '123456789',
});
```

> **XRP destination tag**
>
> A numeric tag (unsigned 32-bit integer) that routes a deposit to the right account behind a
> shared exchange address. Sending without the required tag can strand the funds.

> **Solana memo**
>
> An arbitrary string attached through the SPL Memo program as a separate instruction. The SDK
> depends on `@solana/spl-memo` to build it.

> **Tron memo**
>
> A string attached to TRX and TRC-20 transfers, used by exchanges for deposit identification.

Storing the memo on the entry is bookkeeping only; the transfer methods do not read it back for you. Pass `memo` explicitly on `sendTransaction` / `createSendOrder` when you send. See [Transactions](https://sdk-guide.mintlify.site/sdk-guide/transactions.md) and [Orders](https://sdk-guide.mintlify.site/sdk-guide/orders.md).

### Uniqueness

Two separate 409 conditions apply, both scoped to your personal entries:

| Condition | Message |
| - | - |
| The name is already used (compared case-insensitively) | `Address book entry with name "<name>" already exists` |
| The same `(address, chainType)` pair is already saved (address compared case-insensitively) | `Address book entry for this address and chain type already exists` |

> [!WARNING]
>
> The name check runs across all your entries, not just the ones on that chain. You cannot save
> `"Treasury"` for both an EVM and a Solana address, so give them distinct names.

## Listing entries

```typescript theme={null}
const all = await sdk.getAddressBookEntries();

const evmOnly = await sdk.getAddressBookEntries({
  chainType: EChainType.EVM,
});

const solExchanges = await sdk.getAddressBookEntries({
  chainType: EChainType.SOL,
  type: EAddressBookEntryType.CEX,
});
```

Both filters are optional. The response is a plain array; this listing is not paginated.

## Updating entries

Only `name` and `memo` are mutable. `address`, `chainType` and `type` are fixed at creation; to change any of them, delete the entry and create a new one.

```typescript theme={null}
await sdk.updateAddressBookEntry({ id: entry.id, name: 'Alice main' });

await sdk.updateAddressBookEntry({ id: entry.id, memo: '999888777' });

await sdk.updateAddressBookEntry({ id: entry.id, memo: null });
```

Passing `memo: null` clears the stored memo; omitting `memo` leaves it untouched. Passing neither `name` nor `memo` is a 400 (`At least one of name or memo must be provided`). A new `name` that collides with another of your entries is a 409.

## Deleting entries

```typescript theme={null}
await sdk.deleteAddressBookEntry({ id: entry.id });
```

Returns `void` (HTTP 204). Deletion is scoped to your own personal entries, so an id belonging to another user or to an organization entry simply matches nothing.

> [!WARNING]
>
> Under forced-address-book enforcement, deleting an entry immediately blocks that address as a
> transfer destination.

## Address type lookup

```typescript theme={null}
const result = await sdk.getAddressType({
  chainType: EChainType.EVM,
  address: '0x5b927c461b71Ce25a74878dBe504492Ab80cbeE4',
});

console.log(result.type); // 'CEX' | 'Wallet' | null
```

The wire response carries three fields:

| Field | Type | Meaning |
| - | - | - |
| `type` | `'CEX' \| 'Wallet' \| null` | Classification of the address from a platform-wide lookup: the first stored entry for that `(address, chainType)` pair, from any user. It can be non-`null` for an address you have never saved. |
| `isInAddressBook` | `boolean` | Whether the address is whitelisted for you: your personal entries when the API key has no organization, or the organization's *verified* entries when it does. |
| `isOnlyExist` | `boolean` | Whether forced-address-book enforcement is currently on for the calling context. |

Only `type` is declared on `AddressTypeResponse`. To read the other two, widen the result:

```typescript theme={null}
type AddressTypeWire = Awaited<ReturnType<typeof sdk.getAddressType>> & {
  isInAddressBook: boolean;
  isOnlyExist: boolean;
};

const check = (await sdk.getAddressType({
  chainType: EChainType.EVM,
  address: recipient,
})) as AddressTypeWire;

if (check.isOnlyExist && !check.isInAddressBook) {
  // Whitelisting is enforced and this recipient is not whitelisted, so the send would fail.
}
```

`getAddressType` sends the address in the request body rather than the URL, so it never ends up in access logs or error trackers. Use the same form in direct HTTP calls.

## Whitelisting

Whitelisting is enforced server-side before a transfer is built, on every chain, for both personal sends and multisig orders. Which book is consulted, and which switch turns it on, depends on whether your API key resolves to an organization.

| Calling context | Switch | Book consulted | Rejection message |
| - | - | - | - |
| No organization | Platform setting `forceGlobalAddressBook` | Your personal entries | `Destination address must be in your Address Book: <address>` |
| Organization | Organization policy `forceAddressBook` | The organization's verified entries | `ADDRESS_NOT_WHITELISTED: <address>` |

Two behaviours worth knowing:

* Organization policy takes precedence. Inside an organization the platform-wide setting is not consulted at all.
* An organization Master is exempt only when acting from a non-SDK client. A Master calling through the SDK is subject to the whitelist like anyone else.

Both messages surface as an `ApiError` with `statusCode: 400`. See [Errors](https://sdk-guide.mintlify.site/sdk-guide/errors.md).

### Checking before you send

```typescript theme={null}
const check = (await sdk.getAddressType({
  chainType: EChainType.EVM,
  address: recipient,
})) as AddressTypeWire;

if (check.isOnlyExist && !check.isInAddressBook) {
  await sdk.createAddressBookEntry({
    name: `Recipient ${Date.now()}`,
    address: recipient,
    chainType: EChainType.EVM,
    type: EAddressBookEntryType.Wallet,
  });
}
```

For a full dry run of a transfer, covering balance, fees, policy and whitelist checks in one call, use `preflightSend` / `preflightOrder` instead of hand-rolling the checks. See [Preflight](https://sdk-guide.mintlify.site/sdk-guide/preflight.md).

> [!TIP]
>
> To validate that a string is even a well-formed address for a chain before you save it or send to
> it, use the exported `validateAddressForChain` / `assertValidAddressForChain` helpers rather than
> a round trip to the API. They are covered in [Transactions](https://sdk-guide.mintlify.site/sdk-guide/transactions.md).

## How the organization address book differs

The organization ("Corporate") address book is a separate list with its own methods
(`listOrgAddresses`, `addOrgAddress`, `updateOrgAddress`, `deleteOrgAddress`), shared by every
member of the organization rather than owned by one user. Writes are Master-only, entries carry
an `isVerified` flag and FATF Travel Rule metadata that the personal book has no concept of, and
only verified entries satisfy the organization whitelist.

> [!NOTE]
>
> Full coverage of the organization methods, roles and Travel Rule fields lives in
> [Organization wallets](https://sdk-guide.mintlify.site/sdk-guide/organization-wallets.md#corporate-address-book). The whitelist
> policy switch itself is documented in [Organization policies](https://sdk-guide.mintlify.site/sdk-guide/organization-policies.md).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.