Skip to main content
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.
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 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, Swift, or Kotlin.
  • The user’s email address. This guide uses user@example.com.
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:
The response contains the token and its expiry:
Send accessToken as Authorization: Bearer YOUR_ACCESS_TOKEN in steps 2 and 3. Request a new token before expiresAt. See Get bearer token.

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.
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 for the full request schema. A 201 response returns the customer. Store its id:
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:
A 201 response returns the wallet with status set to preallocated:
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).
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.
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, 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.
A rejected claim throws the error in 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, Swift, or Kotlin.

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. 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:

Next steps