> ## Documentation Index
> Fetch the complete documentation index at: https://docs.polygon.technology/llms.txt
> Use this file to discover all available pages before exploring further.

# Preallocate and claim wallets

> Create a non-custodial wallet address for a user's email before they sign in, then let the OMS Wallet SDK claim it when they sign in with that email.

Create a non-custodial wallet address for a user before they have signed in to your app. Your backend preallocates the wallet for the user's email address through the OMS API and can fund the address right away. The wallet is receive-only: nobody, including your backend, can sign with it until it is claimed. When the user signs in to your app with that email, the OMS Wallet SDK claims the wallet and the user becomes its sole owner.

Your app does not need the customer ID or the wallet ID. The SDK finds claimable wallets from the email the user signs in with.

> [!NOTE]
>
> Kotlin snippets call `suspend` SDK APIs; run them from a coroutine.

## How it works

**1. Backend: get a bearer token**

Exchange your OMS API key and secret for a short-lived bearer token.

**2. Backend: create a customer**

The customer's `email` is the address that can claim the wallet.

**3. Backend: preallocate a wallet**

OMS returns a wallet ID (`wlt_...`) and an address. The wallet is receive-only until it is claimed.

**4. Backend: fund the address (optional)**

Send funds to the address before or after the claim.

**5. App: sign the user in**

The user signs in with that email. Unless the user already owns a wallet of that type, the SDK claims the wallet and activates it.

**6. App: claim other wallets (optional)**

Claim wallets that sign-in reported but did not claim.

**7. Backend: confirm the claim (optional)**

Check that the user claimed the wallet.

## Prerequisites

* `YOUR_API_KEY` and `YOUR_API_SECRET`: an OMS API key and its secret from the **API Keys** page of the OMS Dashboard. Create them in the same project and environment as your app's publishable key. The OMS API is in early access; see [Get started](https://docs.polygon.technology/api-reference/overview.md#get-started) if your project does not have API keys yet. A wallet preallocated in one project or environment is not claimable from another. The secret is shown only once, when you create the key.
* The OMS API base URL for the same environment as your app's publishable key. The examples use Sandbox, `https://sandbox-api.polygon.technology/v0.13`. For Production, use `https://api.polygon.technology/v0.13`.
* An app that signs users in with the TypeScript, Swift, or Kotlin SDK, version 0.4.0 or later. To build an app, start with the quickstart for [TypeScript](https://docs.polygon.technology/wallets/sdk/typescript/quickstart.md), [Swift](https://docs.polygon.technology/wallets/sdk/swift/quickstart.md), or [Kotlin](https://docs.polygon.technology/wallets/sdk/kotlin/quickstart.md).
* The user's email address. This guide uses `user@example.com`.

> [!WARNING]
>
> Steps 1 to 4 and step 7 run on your backend, and steps 5 and 6 run in your app. Never ship the API secret, or a bearer token created from it, in a client app.

## 1. Get a bearer token (backend)

Exchange the API key and secret for a bearer token:

```bash theme={null}
curl -X POST https://sandbox-api.polygon.technology/v0.13/auth/token \
  -H "Content-Type: application/json" \
  -d '{
    "apiKey": "YOUR_API_KEY",
    "apiSecret": "YOUR_API_SECRET"
  }'
```

The response contains the token and its expiry:

```json theme={null}
{
  "accessToken": "eyJhbGciOiJSUzI1NiIs...",
  "tokenType": "bearer",
  "expiresIn": 3599,
  "expiresAt": "2026-10-27T11:17:09Z",
  "projectId": "prj_...",
  "projectMode": "sandbox"
}
```

Send `accessToken` as `Authorization: Bearer YOUR_ACCESS_TOKEN` in steps 2 and 3. Request a new token before `expiresAt`. See [Get bearer token](https://docs.polygon.technology/api-reference/auth/get-bearer-token.md).

## 2. Create a customer with the user's email (backend)

If the user is already a customer, skip this step and use their existing customer `id`; check that its `email` is the address the user signs in with.

Otherwise, create the customer that the wallet belongs to. Set `email` to the address the user will sign in with. Only a user who signs in with that email can claim the wallet.

```bash theme={null}
curl -X POST https://sandbox-api.polygon.technology/v0.13/customers \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: YOUR_CUSTOMER_IDEMPOTENCY_KEY" \
  -d '{
    "type": "individual",
    "firstName": "Jane",
    "lastName": "Smith",
    "email": "user@example.com"
  }'
```

Generate a new `Idempotency-Key`, such as a UUID, for each request, and reuse it only to retry that same request.

Your project's KYC policy can require more fields for an individual, such as `phone`, `birthDate`, `residentialAddress`, or `identifyingInformation`. If the request returns `400`, add the fields your project requires. See [Create a customer](https://docs.polygon.technology/api-reference/customers/create-a-customer.md) for the full request schema.

A `201` response returns the customer. Store its `id`:

```json theme={null}
{
  "id": "cst_...",
  "type": "individual",
  "email": "user@example.com",
  "firstName": "Jane",
  "lastName": "Smith",
  "status": "active"
}
```

The customer `status` depends on your project's verification policy.

If the response is `202`, creation is still running. Wait the number of seconds in the `Retry-After` header, then repeat the request with the same `Idempotency-Key` until it returns `201`.

## 3. Preallocate the wallet (backend)

Create a non-custodial wallet for the customer. Set `customerId` to the customer `id` from step 2, and set `details.networkFamily` to `evm`, `solana`, or `tron`:

```bash theme={null}
curl -X POST https://sandbox-api.polygon.technology/v0.13/wallets \
  -H "Authorization: Bearer YOUR_ACCESS_TOKEN" \
  -H "Content-Type: application/json" \
  -H "Idempotency-Key: YOUR_WALLET_IDEMPOTENCY_KEY" \
  -d '{
    "type": "noncustodial",
    "customerId": "YOUR_CUSTOMER_ID",
    "details": {
      "networkFamily": "evm"
    }
  }'
```

A `201` response returns the wallet with `status` set to `preallocated`:

```json theme={null}
{
  "id": "wlt_...",
  "object": "wallet",
  "type": "noncustodial",
  "status": "preallocated",
  "customerId": "cst_...",
  "details": {
    "address": "0x1111111111111111111111111111111111111111",
    "networkFamily": "evm"
  }
}
```

Store the wallet `id` and `details.address` with your customer record.

Each email in a project has at most one preallocated wallet per network family, and allocations never expire. Repeating the request for the same email and network family, even with a new `Idempotency-Key`, returns the same wallet. To preallocate wallets on more than one network family, send one request per family.

## 4. Fund the address (optional, backend)

Send assets to `details.address`. For test funds on an `evm` wallet, use Polygon Amoy (`80002`).

> [!WARNING]
>
> Funds sent to a preallocated address cannot be recovered if the wallet is never claimed. If the customer's email might be wrong, fund the address after the user claims the wallet instead.

## 5. Sign the user in (app)

Sign the user in with the email you set on the customer, using either:

* Email OTP for that email.
* An OIDC provider configured for your project whose ID token contains that `email` with `email_verified` set to `true`.

Email matching ignores case, so `User@Example.com` matches `user@example.com`.

If the user signs in with a different email, or with an OIDC provider that does not report the email as verified, no wallet is claimable. Sign-in still succeeds: automatic selection activates a wallet the user already owns or creates a new one, without an error, and the preallocated wallet stays unclaimed.

With the default automatic wallet selection, the SDK activates a wallet of the requested wallet type in this order:

1. A wallet of that type that the user already owns.
2. Otherwise, a claimable wallet of that type, which the SDK claims.
3. Otherwise, a new wallet.

Pass `walletType` to `completeEmailAuth`, `signInWithOidcIdToken`, or the call that starts a redirect sign-in. It defaults to Ethereum. The network families from step 3 map to wallet types as `evm` to Ethereum, `solana` to Solana, and `tron` to Tron.

In these snippets, `omsWallet` is the `OMSWallet` instance your app configured with `YOUR_PUBLISHABLE_KEY`.

**TypeScript**

```typescript theme={null}
import { WalletType } from '@polygonlabs/oms-wallet'

await omsWallet.wallet.startEmailAuth({ email: 'user@example.com' })

// After the user enters the emailed code:
const result = await omsWallet.wallet.completeEmailAuth({
  code: '123456',
  walletType: WalletType.Ethereum,
})

console.log('Active wallet:', result.wallet.id, result.wallet.address)
```

**Swift**

```swift theme={null}
try await omsWallet.wallet.startEmailAuth(email: "user@example.com")

// After the user enters the emailed code:
let auth = try await omsWallet.wallet.completeEmailAuth(
    code: "123456",
    walletType: .ethereum
)

if case .walletSelected(let wallet, _, _, _) = auth {
    print("Active wallet:", wallet.id, wallet.address)
}
```

**Kotlin**

```kotlin theme={null}
import technology.polygon.omswallet.models.WalletType
import technology.polygon.omswallet.wallet.CompleteAuthResult

omsWallet.wallet.startEmailAuth(email = "user@example.com")

// After the user enters the emailed code:
val result = omsWallet.wallet.completeEmailAuth(
    code = "123456",
    walletType = WalletType.Ethereum,
)
check(result is CompleteAuthResult.WalletSelected)

println("Active wallet: ${result.wallet.id} ${result.wallet.address}")
```

If the user owned no Ethereum wallet, the active wallet is the preallocated wallet: its `id` is the `wlt_...` ID and its `address` is the `details.address` from step 3. The user now owns the wallet, it appears in `listWallets`, and the session can sign with it.

If the SDK tries to claim the wallet and the claim fails, sign-in fails with the error in [Handle claim errors](#handle-claim-errors), no session is stored, and the SDK does not create a new wallet instead.

## 6. Claim other wallets (optional, app)

Only a completed sign-in reports claimable wallets; `listWallets` does not include a wallet until it is claimed. A wallet the user does not claim is reported again at the next sign-in.

The sign-in result's `claimableWallets` lists every claimable wallet that sign-in did not claim, of any wallet type.

Claim them with `claimWallet` while the session is active. Each claimed wallet becomes the active wallet, so switch back with `useWallet` if you need to. These snippets continue from the step 5 result.

**TypeScript**

```typescript theme={null}
for (const claimable of result.claimableWallets) {
  const claimed = await omsWallet.wallet.claimWallet({ walletId: claimable.id })
  console.log('Claimed:', claimed.wallet.type, claimed.wallet.address)
}
```

**Swift**

```swift theme={null}
for claimable in auth.claimableWallets {
    let claimed = try await omsWallet.wallet.claimWallet(walletId: claimable.id)
    print("Claimed:", claimed.wallet.type, claimed.wallet.address)
}
```

**Kotlin**

```kotlin theme={null}
for (claimable in result.claimableWallets) {
    val claimed = omsWallet.wallet.claimWallet(claimable.id)
    println("Claimed: ${claimed.wallet.type} ${claimed.wallet.address}")
}
```

A rejected claim throws the error in [Handle claim errors](#handle-claim-errors) and leaves the active wallet unchanged.

To let the user choose between owned, claimable, and new wallets during sign-in, use manual wallet selection and `claimAndSelectWallet`. See the authentication page for [TypeScript](https://docs.polygon.technology/wallets/sdk/typescript/authentication.md#claim-a-preallocated-wallet), [Swift](https://docs.polygon.technology/wallets/sdk/swift/authentication.md#claim-a-preallocated-wallet), or [Kotlin](https://docs.polygon.technology/wallets/sdk/kotlin/authentication.md#claim-a-preallocated-wallet).

## 7. Confirm the claim (optional, backend)

After the user signs in, confirm the claim with either method:

* Repeat the step 3 request with a new `Idempotency-Key`. After the claim, it returns the same wallet with `status` set to `active`.
* Have your app send an OMS Wallet ID token after sign-in, and verify it as described in [backend wallet verification](https://docs.polygon.technology/wallets/sdk/guides/backend-wallet-verification.md). Compare its `wallet_address` claim with the stored `details.address`.

## Handle claim errors

A claim fails with `AccountNotFound` when the wallet is not preallocated for the signed-in user's verified email, was already claimed, or the sign-in has no verified email:

| SDK | Error |
| - | - |
| TypeScript | `OMSWalletRequestError` with `code` `OMS_REQUEST_FAILED`, `status` `404`, and `upstreamError.name` `'AccountNotFound'` |
| Swift | `OMSWalletError` with `code` `.requestFailed`, `status` `404`, and `upstreamError?.name` `"AccountNotFound"` |
| Kotlin | `OMSWalletRequestException` with `code` `OMSWalletErrorCode.RequestFailed`, `status` `404`, and `upstreamError?.name` `"AccountNotFound"` |

## Next steps

* Sessions and access for switching between the user's wallets after a claim: [TypeScript](https://docs.polygon.technology/wallets/sdk/typescript/sessions-and-access.md), [Swift](https://docs.polygon.technology/wallets/sdk/swift/sessions-and-access.md), or [Kotlin](https://docs.polygon.technology/wallets/sdk/kotlin/sessions-and-access.md).
* [Backend wallet verification](https://docs.polygon.technology/wallets/sdk/guides/backend-wallet-verification.md) to confirm on your backend which wallet a user controls.
