Skip to main content
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 walletREST API wallet
Created withcreateWalletPreGen() (SDK)POST /v1/wallets (REST)
User share stored byYour applicationPara’s enclave
Signing requiresuserShare + MPC ceremonyWallet ID + API key
In GET /v1/wallets?status=readyNo (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.
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.
The server SDK handles encryption for you.
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

curl "https://api.beta.getpara.com/v1/enclave/public-key" \
  -H "X-API-Key: sk_..."
Response:
{
  "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:
{
  "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.
Getting ECIES-P256 right is tricky. Use the SDK method unless you have a specific reason not to.

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.
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.
StatusMeaning
200Wallet migrated
400Missing or invalid encryptedPayload, key generation incomplete, or payload targets wrong wallet
404Wallet not found or doesn’t belong to your project
409Wallet already migrated

Step-by-step migration

1

Stop creating SDK pregen wallets

For new wallets, switch to the REST API. Replace createWalletPreGen() calls with POST /v1/wallets. Wallets created via the REST API are already ready for signing — no migration needed.
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@example.com",
    "userIdentifierType": "EMAIL"
  }'
2

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

Migrate existing wallets

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;
    }
    console.error(`Failed to migrate ${walletId}:`, err);
  }
}
4

Verify migration

Migrated wallets appear in ?status=ready results:
curl "https://api.beta.getpara.com/v1/wallets?status=ready&userIdentifier=user@example.com&userIdentifierType=EMAIL" \
  -H "X-API-Key: sk_..."
5

Sign via REST API

No userShare or MPC ceremony needed:
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" }'

SDK method to REST endpoint mapping

SDK methodREST API endpointNotes
createWalletPreGen()POST /v1/walletsREST API persists shares server-side
getUserShare() + partner signingPOST /v1/wallets/{id}/sign-rawNo userShare needed after migration
POST /v1/wallets/{id}/sign-transactionEVM, Solana, and Stellar
POST /v1/wallets/{id}/sign-messageEVM, Solana, Cosmos, and Stellar
POST /v1/wallets/{id}/sign-typed-dataEVM only (EIP-712)
POST /v1/wallets/{id}/transferEVM and Solana only
getWallets()GET /v1/walletsUse ?status=ready to filter to migrated wallets

FAQ

Yes. The wallet works with both the SDK and the REST API after 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.
No. The endpoint returns 409 Conflict. The migration loop above handles this by catching 409s and skipping.
Migration and claiming are separate operations. A migrated wallet can still be claimed by a user. Note that REST API signing only works on unclaimed wallets — once a user claims the wallet, use the SDK signing flow instead.
Standard rate limits apply. See Setup - Rate limits for details.
Only if you’re not using the SDK. para.migrateWalletShare() handles ECIES encryption for you.

Next steps