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

# Migrate SDK pregen wallets to REST API

> Move existing SDK-pregenerated wallets to the REST API so you can sign with just a wallet ID and API key

export const Card = ({imgUrl, title, description, href, horizontal = false, newTab = false}) => {
  const [isHovered, setIsHovered] = useState(false);
  const handleClick = e => {
    e.preventDefault();
    if (newTab) {
      window.open(href, '_blank', 'noopener,noreferrer');
    } else {
      window.location.href = href;
    }
  };
  return <div className={`not-prose relative my-2 p-[1px] rounded-xl transition-all duration-300 ${isHovered ? 'bg-gradient-to-r from-[#FF4E00] to-[#874AE3]' : 'bg-gray-200'}`} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}>
      <a href={href} onClick={handleClick} className={`not-prose flex ${horizontal ? 'flex-row' : 'flex-col'} font-normal h-full bg-white overflow-hidden w-full cursor-pointer rounded-[11px] no-underline`}>
        {imgUrl && <div className={`relative overflow-hidden flex-shrink-0 ${horizontal ? 'w-[30%] rounded-l-[11px]' : 'w-full'}`} onClick={e => e.stopPropagation()}>
            <img src={imgUrl} alt={title} className="w-full h-full object-cover pointer-events-none select-none" draggable="false" />
            <div className="absolute inset-0 pointer-events-none" />
          </div>}
        <div className={`flex-grow px-6 py-5 ${horizontal ? 'w-[70%]' : 'w-full'} flex flex-col ${horizontal && imgUrl ? 'justify-center' : 'justify-start'}`}>
          {title && <h2 className="font-semibold text-base text-gray-800 m-0">{title}</h2>}
          {description && <div className={`font-normal text-gray-500 re leading-6 ${horizontal || !imgUrl ? 'mt-0' : 'mt-1'}`}>
              <p className="m-0 text-xs">{description}</p>
            </div>}
        </div>
      </a>
    </div>;
};

Wallets created with `createWalletPreGen()` require the stored `userShare` and an MPC ceremony every time you sign. After migration, signing only needs a wallet ID and your API key.

## SDK pregen vs REST API wallets

Both wallet types use the same database and MPC infrastructure. The difference is where the user's key share lives.

|                                   | SDK pregen wallet            | REST API wallet           |
| --------------------------------- | ---------------------------- | ------------------------- |
| Created with                      | `createWalletPreGen()` (SDK) | `POST /v1/wallets` (REST) |
| User share stored by              | Your application             | Para's enclave            |
| Signing requires                  | `userShare` + MPC ceremony   | Wallet ID + API key       |
| In `GET /v1/wallets?status=ready` | No (until migrated)          | Yes                       |

## What happens during migration

1. The SDK encrypts your `userShare` with the enclave's P-256 public key (ECIES). The plaintext never leaves your server.
2. The encrypted payload is sent to Para's backend, which forwards it to the hardware-isolated enclave for decryption and storage.
3. The wallet becomes eligible for REST API signing and appears in `GET /v1/wallets?status=ready` queries.

<Info>
  Migration is additive — you're adding REST API access, not replacing SDK signing. After verifying migration, you can optionally delete the stored `userShare` values from your database if you no longer need SDK-based signing.
</Info>

<Warning>
  Only unclaimed pregen wallets can be migrated. Once a user signs up with the wallet's pregen identifier, migration returns `403`.
</Warning>

## Migrate with the SDK (recommended)

The server SDK handles encryption for you.

```typescript theme={null}
import { Para as ParaServer, Environment } from "@getpara/server-sdk";

const para = new ParaServer(Environment.BETA, 'your-api-key'); // regular API key
const secretApiKey = 'sk_beta_...'; // secret key (sk_ prefix)

// Migrate a single wallet
await para.migrateWalletShare({ walletId, userShare, secretApiKey });

// Solana or Stellar wallets need the scheme specified
await para.migrateWalletShare({ walletId, userShare, secretApiKey, walletScheme: 'ED25519' });
```

This method fetches the enclave's public key, encrypts the share with ECIES-P256-AES256-SHA256, and sends the encrypted payload to `POST /v1/wallets/{walletId}/migrate-share`.

EVM and Cosmos wallets use the `DKLS` scheme (the default). Pass `'ED25519'` for Solana or Stellar wallets.

## Migrate with the REST API directly

If you're not using the SDK, you'll need to encrypt the share yourself before calling the endpoint.

### Fetch the enclave public key

```bash theme={null}
curl "https://api.beta.getpara.com/v1/enclave/public-key" \
  -H "X-API-Key: sk_..."
```

Response:

```json theme={null}
{
  "publicKey": "-----BEGIN PUBLIC KEY-----\nMFkwEwYH...base64...\n-----END PUBLIC KEY-----",
  "keyFingerprint": "SHA256:abc123...",
  "generatedAt": "2025-01-15T00:00:00.000Z"
}
```

### Encrypt the share

Implement ECIES encryption with the P-256 curve:

1. Generate an ephemeral P-256 key pair
2. Run ECDH with the enclave's public key to derive a shared secret
3. SHA-256 hash the shared secret to produce the AES key
4. Encrypt the share data with AES-256-GCM using a random 12-byte IV
5. Prepend the IV to the ciphertext (which includes the GCM auth tag), then base64-encode the result into `encryptedData`

The plaintext you encrypt is a JSON string:

```json theme={null}
{
  "shares": [{
    "userId": "your-wallet-id",
    "walletId": "your-wallet-id",
    "walletScheme": "DKLS",
    "signer": "raw-signer-extracted-from-user-share"
  }]
}
```

The `signer` field is the raw signer secret extracted from the `userShare` string. The `userShare` is a series of base64-encoded JSON segments joined by `-`. Parse each segment, find the one whose `id` matches your wallet ID, and use its `signer` field.

Set `userId` to the wallet ID. Pregen wallets don't have a user ID, but the enclave schema requires a non-empty value — the wallet ID is used as a placeholder and is ignored during signing.

<Warning>
  Getting ECIES-P256 right is tricky. Use the SDK method unless you have a specific reason not to.
</Warning>

### Send the encrypted payload

The `encryptedPayload` value is a **JSON string**, not a nested object. Stringify your ECIES envelope before embedding it in the request body.

```bash theme={null}
curl -X POST "https://api.beta.getpara.com/v1/wallets/WALLET_ID/migrate-share" \
  -H "X-API-Key: sk_..." \
  -H "Content-Type: application/json" \
  -d '{ "encryptedPayload": "{\"encryptedData\":\"...\",\"keyId\":\"\",\"ephemeral\":\"...\",\"algorithm\":\"ECIES-P256-AES256-SHA256\"}" }'
```

Maximum payload size is 64KB. Returns the updated wallet object on success.

| Status | Meaning                                                                                           |
| ------ | ------------------------------------------------------------------------------------------------- |
| `200`  | Wallet migrated                                                                                   |
| `400`  | Missing or invalid `encryptedPayload`, key generation incomplete, or payload targets wrong wallet |
| `403`  | Wallet already claimed by a user — not migratable                                                 |
| `404`  | Wallet not found or doesn't belong to your project                                                |
| `409`  | Wallet already migrated                                                                           |

## Step-by-step migration

<Steps>
  <Step title="Stop creating SDK pregen wallets">
    For new wallets, switch to the REST SDK or REST API. Replace `createWalletPreGen()` calls with
    `para.createWallet()` from [`@getpara/rest-sdk`](/v2/rest/sdk), or call `POST /v1/wallets` directly. Wallets
    created via REST are already ready for signing — no migration needed.

    ```typescript theme={null}
    import { ParaRestClient } from "@getpara/rest-sdk";

    const para = new ParaRestClient({
      apiKey: process.env.PARA_API_KEY!,
      env: "BETA",
    });

    await para.createWallet({
      type: "EVM",
      userIdentifier: "user@test.getpara.com",
      userIdentifierType: "EMAIL",
    });
    ```

    ```bash theme={null}
    curl -X POST "https://api.beta.getpara.com/v1/wallets" \
      -H "X-API-Key: sk_..." \
      -H "Content-Type: application/json" \
      -d '{
        "type": "EVM",
        "userIdentifier": "user@test.getpara.com",
        "userIdentifierType": "EMAIL"
      }'
    ```
  </Step>

  <Step title="Export existing user shares">
    Gather the `userShare` values you stored when you created each SDK pregen wallet. You need the `walletId` and its corresponding `userShare` for every wallet you want to migrate.
  </Step>

  <Step title="Migrate existing wallets">
    ```typescript theme={null}
    import { Para as ParaServer, Environment } from "@getpara/server-sdk";

    const para = new ParaServer(Environment.BETA, 'your-api-key');
    const secretApiKey = 'sk_beta_...';

    for (const { walletId, userShare } of walletsToMigrate) {
      try {
        await para.migrateWalletShare({ walletId, userShare, secretApiKey });
        console.log(`Migrated ${walletId}`);
      } catch (err) {
        if (err.status === 409) {
          console.log(`${walletId} already migrated, skipping`);
          continue;
        }
        if (err.status === 403) {
          console.log(`${walletId} already claimed, skipping`);
          continue;
        }
        console.error(`Failed to migrate ${walletId}:`, err);
      }
    }
    ```
  </Step>

  <Step title="Verify migration">
    Migrated wallets appear in `?status=ready` results:

    ```bash theme={null}
    curl "https://api.beta.getpara.com/v1/wallets?status=ready&userIdentifier=user@example.com&userIdentifierType=EMAIL" \
      -H "X-API-Key: sk_..."
    ```
  </Step>

  <Step title="Sign via REST API">
    No `userShare` or MPC ceremony needed:

    ```typescript theme={null}
    const { signature } = await para.signMessage("WALLET_ID", {
      message: "Hello from REST API",
    });
    ```

    ```bash theme={null}
    curl -X POST "https://api.beta.getpara.com/v1/wallets/WALLET_ID/sign-message" \
      -H "X-API-Key: sk_..." \
      -H "Content-Type: application/json" \
      -d '{ "message": "Hello from REST API" }'
    ```
  </Step>
</Steps>

## SDK method to REST endpoint mapping

| SDK method                         | REST API endpoint                        | Notes                                             |
| ---------------------------------- | ---------------------------------------- | ------------------------------------------------- |
| `createWalletPreGen()`             | `POST /v1/wallets`                       | REST API persists shares server-side              |
| `getUserShare()` + partner signing | `POST /v1/wallets/{id}/sign-raw`         | No `userShare` needed after migration             |
| —                                  | `POST /v1/wallets/{id}/sign-transaction` | EVM, Solana, and Stellar                          |
| —                                  | `POST /v1/wallets/{id}/sign-message`     | EVM, Solana, Cosmos, and Stellar                  |
| —                                  | `POST /v1/wallets/{id}/sign-typed-data`  | EVM only (EIP-712)                                |
| —                                  | `POST /v1/wallets/{id}/transfer`         | EVM and Solana only                               |
| `getWallets()`                     | `GET /v1/wallets`                        | Use `?status=ready` to filter to migrated wallets |

## FAQ

<AccordionGroup>
  <Accordion title="Can I still use the SDK after migration?">
    Yes. The wallet works with both the SDK and the REST API after migration.
  </Accordion>

  <Accordion title="Can I undo a migration?">
    No. Once the share is persisted to the enclave, it can't be removed. The wallet remains usable through both the SDK and REST API.
  </Accordion>

  <Accordion title="Can I migrate a wallet that was already migrated?">
    No. The endpoint returns `409 Conflict`. The migration loop above handles this by catching 409s and skipping.
  </Accordion>

  <Accordion title="Can I migrate a claimed wallet?">
    No. Migration only works on unclaimed pregen wallets — it returns `403` once a user has claimed the wallet.
  </Accordion>

  <Accordion title="Is there a rate limit?">
    Standard rate limits apply. See [Setup - Rate limits](/v2/rest/setup#rate-limits) for details.
  </Accordion>

  <Accordion title="Do I need to handle encryption myself?">
    Only if you're not using the SDK. `para.migrateWalletShare()` handles ECIES encryption for you.
  </Accordion>
</AccordionGroup>

## Next steps

<CardGroup cols={2}>
  <Card title="REST API setup" imgUrl="/images/v2/general-server.png" href="/v2/rest/setup" description="Authentication, error handling, and rate limits" />

  <Card title="REST API overview" imgUrl="/images/v2/general-server.png" href="/v2/rest/overview" description="Full list of signing and wallet endpoints" />

  <Card title="Multi-wallet setup" imgUrl="/images/v2/general-server.png" href="/v2/rest/multi-wallet" description="Create multiple wallets per user with CUSTOM_ID" />

  <Card title="Example" imgUrl="/images/v2/general-examples.png" href="https://github.com/getpara/examples-hub/tree/2.0.0/server/rest-with-node" description="Full Node.js REST API example on GitHub" />
</CardGroup>
