> Solana Developer Platform docs, served as Markdown. The [docs index](https://platform-beta.solana.com/docs.md) lists every page and [llms.txt](https://platform-beta.solana.com/docs/ai/llms.txt) the key ones; [SKILL.md](https://platform-beta.solana.com/SKILL.md) has the rules for building on SDP.

# Create a wallet

Set up a custody provider and create wallets for signing transactions.

A **custody wallet** is a Solana keypair whose key a custody provider holds. SDP sends each signing request to the provider that holds the wallet's key. Set up a provider, then create wallets for transfers and other onchain operations.

## Supported providers

| Provider | Description |
| --- | --- |
| **Privy** | Embedded wallet infrastructure platform |
| **[Fireblocks](/docs/wallet-operations/fireblocks)** | Digital asset infrastructure company |
| **Coinbase CDP** | Embedded wallets for developers |
| **Para** | Wallet and authentication suite |
| **Turnkey** | Non-custodial wallet infrastructure platform |
| **DFNS** | Digital asset wallet infrastructure |
| **IBM Digital Asset Haven** | Institutional custody on IBM Digital Asset Haven |
| **Anchorage** | Regulated institutional crypto custody |
| **Utila** | Vault-backed wallet signing through service accounts |

- [Connect Fireblocks](/docs/wallet-operations/fireblocks): Connect your own Fireblocks workspace for production custody.

**Dashboard**

### 1. Open wallet setup

Open **Wallets** in the sidebar and click **Create wallet**. Once the project has wallets, the button reads **Create a wallet**. Only organization admins can create wallets. Complete [organization setup](/docs/guides/setup-organization) first if needed.

### 2. Choose a custody provider

Under **Which provider holds the keys?**, select a provider. You can't change it after the wallet exists. When your project can use only one provider, it's already selected and the question isn't shown.

### 3. Choose whose account holds the keys

If you can connect your own account for the provider, the page asks whose account holds the keys, such as **Whose Privy account holds the keys?** When there's a choice, nothing is preselected.

- **SDP's Privy account**: for trying SDP out in Sandbox. It has no wallet policy and nothing to set up.
- **Your own Privy account**: connect it with its app ID and secret. Production accepts only your own account; see [Go to mainnet](/docs/guides/go-to-mainnet).

### 4. Create the first wallet

With SDP's account, enter a **Wallet label** and click **Create wallet**. The wallet's page opens with its address and balance.

With your own account, keep or rename the **Credential label**, and enter your **Privy app ID** and **Privy app secret**. Under **Wallet details**, enter a **Wallet label**. Click **Connect and create wallet**: SDP checks the credentials with Privy, then creates the wallet, which appears on **Wallets**.

![Privy provider setup with example credentials and a masked secret](/images/go-to-mainnet/04-connect-privy.png)

The first wallet in a provider account is a transfer wallet.

### 5. Create more wallets

On **Wallets**, click **Create a wallet** and select the provider if asked. The account question lists SDP's account where it's offered, each of your connections, and **Connect another Privy account**. A connection that isn't ready shows what it needs, with a link to fix it.

For SDP's account or an existing connection, enter a **Wallet label**, pick a **Purpose** (it starts on **Transfers**), and click **Create wallet**. **Connect another Privy account** works as in step 4.

A connection signs only with its current credentials. If you rotate or remove them while a request is signing, that request returns `409 CONFLICT` with `details.reason` set to `runtime_execution_unavailable`. Retry it as [Idempotency](/docs/developing-with-sdp/idempotency) describes for that operation. A transfer that recorded the failure replays it under the same `Idempotency-Key`, so send the transfer again with a new key.

**API**

#### 1. Set up the provider in the dashboard

Setting up a custody provider takes `wallets.configure`, and no API key can hold a `configure` action. An organization admin sets it up once per project, signed in to the dashboard (see the **Dashboard** tab), which calls `POST /v1/wallets/initialize` with that session. The same call with an API key returns `403 INSUFFICIENT_PERMISSIONS` with `details.action` set to `wallets.configure`.

Production projects take only your own provider account. `POST /v1/wallets/initialize` sets up SDP's account of the provider (Managed custody), so in a Production project it returns `403` with `details.reason` set to `custody_mode_not_allowed`. Connect your own account to the project in the dashboard instead. Then create wallets in that connection with `connectionId`, using an API key that holds `wallets.manage`:

```bash title="Terminal"
export IDEMPOTENCY_KEY=$(uuidgen)

curl -X POST https://api.solana.com/v1/wallets \
  -H "Authorization: Bearer sf_..." \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "connectionId": "cconn_123",
    "label": "Treasury wallet"
  }'
```

#### 2. Create more wallets

Every wallet names the provider account it lives under: `provider` for SDP's account of that provider (Managed), or `connectionId` for a connection your project added. Send exactly one of them; a body with both or neither returns `400`. There is no default account to fall back to.

**curl**

```bash
IDEMPOTENCY_KEY=$(uuidgen)
curl -X POST 'https://api.solana.com/v1/wallets' \
  -H "Authorization: Bearer $SDP_API_KEY" \
  -H "Idempotency-Key: $IDEMPOTENCY_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "provider": "privy",
    "label": "Treasury wallet",
    "purpose": "transfer"
  }'
```

**TypeScript**

```typescript
// Reuse this key when you retry this request.
const idempotencyKey = crypto.randomUUID();
const res = await fetch("https://api.solana.com/v1/wallets", {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.SDP_API_KEY}`,
    "Idempotency-Key": idempotencyKey,
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    "provider": "privy",
    "label": "Treasury wallet",
    "purpose": "transfer"
  })
});
```

**Java**

```java title="CreateWallet.java"
String idempotencyKey = java.util.UUID.randomUUID().toString();
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.solana.com/v1/wallets"))
    .header("Authorization", "Bearer sf_...")
    .header("Idempotency-Key", idempotencyKey)
    .header("Content-Type", "application/json")
    .POST(HttpRequest.BodyPublishers.ofString("""
        {
          "provider": "privy",
          "label": "Treasury wallet",
          "purpose": "transfer"
        }"""))
    .build();
```

`purpose` is optional and defaults to `transfer`. Only a `transfer` wallet can be the source of a transfer, transfer batch, or recurring payment; any other purpose is refused with `403` and `details.reason` set to `wallet_purpose`. Reuse the same `Idempotency-Key` when you retry a wallet creation.

#### 3. List wallets

**curl**

```bash title="Terminal"
curl "https://api.solana.com/v1/wallets?view=summary" \
  -H "Authorization: Bearer sf_..."
```

**TypeScript**

```typescript title="list-wallets.ts"
const response = await fetch("https://api.solana.com/v1/wallets?view=summary", {
  headers: { "Authorization": "Bearer sf_..." },
});
const { data } = await response.json();
// data.wallets includes id, walletId, publicKey, provider, label, purpose, and status
```

**Java**

```java title="ListWallets.java"
HttpRequest request = HttpRequest.newBuilder()
    .uri(URI.create("https://api.solana.com/v1/wallets?view=summary"))
    .header("Authorization", "Bearer sf_...")
    .GET()
    .build();
```

Every operation that signs names its wallet; there is no default wallet. Transfers take the wallet's `id` (`cwlt_…`) as `sourceCustodyWalletId`, and other modules' pages name their field. An API key limited to selected wallets lists the same `id` in `walletIds`.

```typescript title="transfer-request.ts"
const body = {
  sourceCustodyWalletId: "cwlt_123", // `id` from GET /v1/wallets
  destination: "7xKXz...9fGh",
  token: "9aBCd...4eEf",
  amount: "100.00",
};
```
