# Pregenerating an embedded wallet

You can pregenerate one or more non-custodial wallets associated with a given account, such as an email address or third-party authentication provider, without requiring the user to log in. You can send assets to the wallets before the user logs in for the first time. Once the user logs in, they can access the pregenerated wallets and any assets sent to them.

A single pregeneration call creates the user record plus the embedded accounts you request — so you can reserve, for example, both an EVM and a Solana address for the same user in one round trip. See [Pregenerate multiple wallets in one call](#pregenerate-multiple-wallets-in-one-call) below.

Users claim their pregenerated wallets by logging into your app with one of the linked accounts.

<MultiOptionDisplay
  options={[
  { id: 'ethereum', label: 'Ethereum' },
  { id: 'solana', label: 'Solana' },
]}
/>

<span id="ethereum" className="hidden [&>*]:mb-6!">
  :::code-group

  ```ts [Ethereum with email]
  // Set your secret key. Remember to switch to your live secret key in production.
  // See your keys here: https://dashboard.openfort.io/api-keys
  import Openfort, { ShieldAuthProvider } from '@openfort/openfort-node';
  const openfort = new Openfort(YOUR_SECRET_KEY);

  // Pregenerate an Ethereum embedded wallet using email
  const account = await openfort.accounts.evm.embedded.pregenerate(
    {
      email: "user@example.com",
    },
    {
      shieldAuthProvider: ShieldAuthProvider.OPENFORT,
      shieldApiKey: 'YOUR_SHIELD_PUBLISHABLE_KEY',
      shieldApiSecret: 'YOUR_SHIELD_API_SECRET',
      encryptionShare: 'YOUR_SHIELD_ENCRYPTION_SHARE',
    }
  )
  ```

  ```ts [Ethereum with third-party auth]
  // Set your secret key. Remember to switch to your live secret key in production.
  // See your keys here: https://dashboard.openfort.io/api-keys
  import Openfort, { ShieldAuthProvider, ThirdPartyOAuthProvider } from '@openfort/openfort-node';
  const openfort = new Openfort(YOUR_SECRET_KEY);

  // Pregenerate an Ethereum embedded wallet using a third-party auth provider
  const account = await openfort.accounts.evm.embedded.pregenerate(
    {
      thirdPartyUserId: "firebase_user_123",
      thirdPartyProvider: ThirdPartyOAuthProvider.firebase,
    },
    {
      shieldAuthProvider: ShieldAuthProvider.OPENFORT,
      shieldApiKey: 'YOUR_SHIELD_PUBLISHABLE_KEY',
      shieldApiSecret: 'YOUR_SHIELD_API_SECRET',
      encryptionShare: 'YOUR_SHIELD_ENCRYPTION_SHARE',
    }
  )
  ```

  :::
</span>

<span id="solana" className="hidden [&>*]:mb-6!">
  :::code-group

  ```ts [Solana with email]
  // Set your secret key. Remember to switch to your live secret key in production.
  // See your keys here: https://dashboard.openfort.io/api-keys
  import Openfort, { ShieldAuthProvider } from '@openfort/openfort-node';
  const openfort = new Openfort(YOUR_SECRET_KEY);

  // Pregenerate a Solana embedded wallet using email
  const account = await openfort.accounts.solana.embedded.pregenerate(
    {
      email: "user@example.com",
    },
    {
      shieldAuthProvider: ShieldAuthProvider.OPENFORT,
      shieldApiKey: 'YOUR_SHIELD_PUBLISHABLE_KEY',
      shieldApiSecret: 'YOUR_SHIELD_API_SECRET',
      encryptionShare: 'YOUR_SHIELD_ENCRYPTION_SHARE',
    }
  )
  ```

  ```ts [Solana with third-party auth]
  // Set your secret key. Remember to switch to your live secret key in production.
  // See your keys here: https://dashboard.openfort.io/api-keys
  import Openfort, { ShieldAuthProvider, ThirdPartyOAuthProvider } from '@openfort/openfort-node';
  const openfort = new Openfort(YOUR_SECRET_KEY);

  // Pregenerate a Solana embedded wallet using a third-party auth provider
  const account = await openfort.accounts.solana.embedded.pregenerate(
    {
      thirdPartyUserId: "supabase_user_456",
      thirdPartyProvider: ThirdPartyOAuthProvider.supabase,
    },
    {
      shieldAuthProvider: ShieldAuthProvider.OPENFORT,
      shieldApiKey: 'YOUR_SHIELD_PUBLISHABLE_KEY',
      shieldApiSecret: 'YOUR_SHIELD_API_SECRET',
      encryptionShare: 'YOUR_SHIELD_ENCRYPTION_SHARE',
    }
  )
  ```

  :::
</span>

:::tip\[Runnable example]
See the [pregenerate example](https://github.com/openfort-xyz/openfort-node/tree/main/examples/evm/embedded/pregenerate.ts) in the Node SDK repository.
:::

Provide one of the following user identification methods:

* `email`: the user's email address when using Openfort authentication.
* `thirdPartyUserId` and `thirdPartyProvider`: the user's ID and provider when using a third-party authentication service such as Firebase, Supabase, or Auth0. Both fields are required when using this method.

For the shield configuration:

* `shieldAuthProvider`: set to `ShieldAuthProvider.OPENFORT`.
* `shieldApiKey`: your Shield API key from the dashboard.
* `shieldApiSecret`: your Shield API secret from the dashboard.
* `encryptionShare`: your Shield encryption share.

The wrapper pre-registers the recovery share with Shield (entropy `project`, automatic / project-managed recovery) before returning, so the resolved value contains the embedded account but **does not include the recovery share** — Shield is the source of truth from this point on.

Each account entry follows the same shape regardless of chain or account type. Required fields are always present; optional fields appear depending on the account type:

| Field                   | Type                       | When present                                                                                  |
| ----------------------- | -------------------------- | --------------------------------------------------------------------------------------------- |
| `id`                    | string (`acc_…`)           | always                                                                                        |
| `wallet`                | string (`pla_…`)           | always — the owning player; shared across all accounts pregenerated for the same user         |
| `signer`                | string (`sig_…`)           | always — the embedded signer backing this account                                             |
| `accountType`           | string                     | always — `"Externally Owned Account"`, `"Smart Account"`, or `"Delegated Account"`            |
| `address`               | string                     | always — `0x…` for EVM, base58 for SVM                                                        |
| `chainType`             | `"EVM"` | `"SVM"`         | always                                                                                        |
| `custody`               | `"Developer"` | `"User"`  | always — pregenerated embedded accounts are `"User"`                                          |
| `createdAt`, `updatedAt`| number (unix seconds)      | always                                                                                        |
| `chainId`               | number                     | Smart Account and Delegated Account entries                                                   |
| `smartAccount`          | object                     | Smart Account and Delegated Account entries; see fields below                                 |
| `recoveryMethod`        | string                     | only on `GET /v2/accounts` reads after Shield resolves the share, not on the pregenerate response |
| `recoveryMethodDetails` | object                     | same as above                                                                                 |
| `ownerAddress`          | string                     | deprecated, omitted for EOA and Delegated entries                                             |

`smartAccount` (Smart Account / Delegated Account entries):

| Field                    | Type     | When present                                                                       |
| ------------------------ | -------- | ---------------------------------------------------------------------------------- |
| `implementationType`     | string   | always — e.g. `"UpgradeableV5"`, `"Calibur"`                                       |
| `implementationAddress`  | string   | always                                                                             |
| `active`                 | boolean  | always — `false` until the account is deployed on-chain                            |
| `factoryAddress`         | string   | Smart Account entries; omitted for Delegated Account (no counterfactual deploy)    |
| `salt`                   | string   | Smart Account entries; omitted for Delegated Account                               |
| `deployedTx`             | string   | only after deployment                                                              |
| `deployedAt`             | number   | only after deployment                                                              |

EOA responses (`accountType: "Externally Owned Account"`) for the per-chain wrappers above:

:::code-group

```json [Ethereum Response]
{
  "id": "acc_ff54b031-a878-4ca2-9cf5-ae190f921e9b",
  "wallet": "pla_ff54b031-a878-4ca2-9cf5-ae190f921e9b",
  "signer": "sig_ff54b031-a878-4ca2-9cf5-ae190f921e9b",
  "accountType": "Externally Owned Account",
  "address": "0x1234567890abcdef1234567890abcdef12345678",
  "chainType": "EVM",
  "custody": "User",
  "createdAt": 1691658234,
  "updatedAt": 1691658234
}
```

```json [Solana Response]
{
  "id": "acc_ff54b031-a878-4ca2-9cf5-ae190f921e9b",
  "wallet": "pla_ff54b031-a878-4ca2-9cf5-ae190f921e9b",
  "signer": "sig_ff54b031-a878-4ca2-9cf5-ae190f921e9b",
  "accountType": "Externally Owned Account",
  "address": "7EcDhSYGxXyscszYEp35KHN8vvw3svAuLKTzXwCFLtV",
  "chainType": "SVM",
  "custody": "User",
  "createdAt": 1691658234,
  "updatedAt": 1691658234
}
```

:::

## Pregenerate multiple wallets in one call

Each user identifier (email or third-party user ID) can only be pregenerated once, so you cannot call `accounts.evm.embedded.pregenerate` and then `accounts.solana.embedded.pregenerate` for the same email — the second call returns `409 Conflict`. To reserve more than one wallet for the same user (for example one EVM and one Solana address), use the chain-agnostic `accounts.embedded.pregenerate` wrapper with an `accounts` array. Each entry produces its own key material, its own recovery share, and each share is pre-registered with Shield separately:

```ts [Pregenerate EVM + Solana for one user]
// Set your secret key. Remember to switch to your live secret key in production.
// See your keys here: https://dashboard.openfort.io/api-keys
import Openfort, { ShieldAuthProvider } from '@openfort/openfort-node';
const openfort = new Openfort(YOUR_SECRET_KEY);

const { user, accounts } = await openfort.accounts.embedded.pregenerate(
  {
    email: 'user@example.com',
    accounts: [
      // Smart Account on EVM — chainId and implementationType are required
      { chainType: 'EVM', accountType: 'Smart Account', chainId: 137, implementationType: 'UpgradeableV5' },
      // EOA on Solana — no chainId / implementationType needed
      { chainType: 'SVM', accountType: 'Externally Owned Account' },
    ],
  },
  {
    shieldAuthProvider: ShieldAuthProvider.OPENFORT,
    shieldApiKey: 'YOUR_SHIELD_PUBLISHABLE_KEY',
    shieldApiSecret: 'YOUR_SHIELD_API_SECRET',
    encryptionShare: 'YOUR_SHIELD_ENCRYPTION_SHARE',
  },
);

// user        -> "usr_..."
// accounts[0] -> EVM account (chainType: "EVM")
// accounts[1] -> Solana account (chainType: "SVM")
```

Each entry in `accounts` is a `PregenerateAccountConfig` and accepts the same per-account fields as the single-wallet helpers — `accountType`, `chainType`, `chainId`, `implementationType` — so you can mix Smart Accounts, Delegated Accounts and EOAs across chain types in a single request. The same defaults apply per entry: `accountType` defaults to `"Externally Owned Account"` and `chainType` defaults to `"EVM"`.

A successful response has the user ID at the top level and the resolved accounts in the same order as the request, each one pre-registered with Shield (recovery share omitted):

```json [Multi-chain Response]
{
  "user": "usr_ff54b031-a878-4ca2-9cf5-ae190f921e9b",
  "accounts": [
    {
      "id": "acc_aaaa1111-...",
      "wallet": "pla_ff54b031-a878-4ca2-9cf5-ae190f921e9b",
      "signer": "sig_aaaa1111-...",
      "accountType": "Smart Account",
      "address": "0x1234567890abcdef1234567890abcdef12345678",
      "chainType": "EVM",
      "chainId": 137,
      "smartAccount": {
        "implementationType": "UpgradeableV5",
        "factoryAddress": "0xcb71e008b9062bb7abd558816f8135ef2cab576f",
        "implementationAddress": "0xc8ebbdd2da25906b96c374c9b1757b1421e9f45a",
        "salt": "0x02381f3ecfe2f494e997d1f89c286abe8c3ca572edb607092a52d0c92fc47c31",
        "active": false
      },
      "custody": "User",
      "createdAt": 1691658234,
      "updatedAt": 1691658234
    },
    {
      "id": "acc_bbbb2222-...",
      "wallet": "pla_ff54b031-a878-4ca2-9cf5-ae190f921e9b",
      "signer": "sig_bbbb2222-...",
      "accountType": "Externally Owned Account",
      "address": "7EcDhSYGxXyscszYEp35KHN8vvw3svAuLKTzXwCFLtV",
      "chainType": "SVM",
      "custody": "User",
      "createdAt": 1691658234,
      "updatedAt": 1691658234
    }
  ]
}
```

Both accounts share the same `wallet`, and the user logs in once to reach both. If any Shield pre-registration fails midway through, the just-created user is rolled back so you don't end up with a half-set-up account.
