Skip to Content
⚙️ Core SDKServer Wallets

Server Wallets

Create and manage MPC wallets from your backend using ServerWalletManager.

Server wallets let you create, sign, and transact with smart accounts entirely from your backend — no browser, no user interaction required. They use the same DKLS23 Threshold Signature Scheme as client-side wallets, but authentication is via API keys instead of user sessions.

Use Cases

  • Custodial wallets — Manage wallets on behalf of users
  • Treasury management — Automated fund transfers and multi-sig operations
  • Game economies — In-game currency and NFT management
  • Service accounts — Bots, automated trading, payment processing

Installation

npm install @embarkai/core

The dkls23-wasm module is loaded automatically when needed for DKG and signing operations.

Quick Start

import { createServerWalletManager, MemoryKeyshareStorage, } from '@embarkai/core' const manager = createServerWalletManager({ apiKey: process.env.EMBARK_API_KEY!, chainId: 994873017, // Lumia Mainnet storage: new MemoryKeyshareStorage(), // Dev only! Use secure storage in production }) // Create a wallet (runs DKG protocol internally) const wallet = await manager.createWallet('treasury_main') console.log('Owner (EOA):', wallet.ownerAddress) console.log('Smart Account:', wallet.smartAccountAddress) // Send a transaction const userOpHash = await wallet.sendUserOperation( '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', '1000000000000000000', // 1 LUMIA in wei ) // Wait for confirmation const receipt = await wallet.waitForUserOperationReceipt(userOpHash) console.log('TX Hash:', receipt.transactionHash) console.log('Success:', receipt.success)

Configuration

interface ServerWalletManagerConfig { /** Project API key from EmbarkAI Dashboard (required) */ apiKey: string /** Keyshare storage implementation (required) */ storage: KeyshareStorage /** Target chain ID (required) */ chainId: number /** Override TSS API URL */ tssUrl?: string /** Override bundler RPC URL (resolved from chainId if omitted) */ bundlerUrl?: string /** Override blockchain RPC URL (resolved from chainId if omitted) */ rpcUrl?: string /** Override AA factory address (resolved from chainId if omitted) */ factoryAddress?: `0x${string}` /** Override EntryPoint v0.7 address (resolved from chainId if omitted) */ entryPointAddress?: `0x${string}` /** Override paymaster address for sponsored transactions */ paymasterAddress?: `0x${string}` /** Max call gas limit for UserOperations (default: 5,000,000) */ maxCallGasLimit?: bigint /** Max verification gas limit (default: 5,000,000) */ maxVerificationGasLimit?: bigint /** Enable debug logging (default: false) */ debug?: boolean }

Most fields are resolved automatically from built-in chain configurations when you provide a supported chainId. You only need apiKey, storage, and chainId to get started.

API Key Setup

  1. Go to the EmbarkAI Dashboard .
  2. Select your project.
  3. Navigate to API Keys.
  4. Click Generate Server API Key.
  5. Copy the key (shown only once) and store it in your environment.
# .env EMBARK_API_KEY=lp_00ab351d95c0eaf3ee499006a6a32402c31177bef4c7c1f26fed8cb947e15966

Keyshare Storage

When a wallet is created via DKG, the manager stores the client-side keyshare using your provided KeyshareStorage implementation. This keyshare is required for all future signing operations.

Storage Interface

interface KeyshareStorage { set(userId: string, keyshare: string): Promise<void> | void get(userId: string): Promise<string | null> | string | null has(userId: string): Promise<boolean> | boolean list(): Promise<string[]> | string[] }

Built-in: MemoryKeyshareStorage

For development and testing only. Keyshares are lost when the process exits.

import { MemoryKeyshareStorage } from '@embarkai/core' const storage = new MemoryKeyshareStorage()

Custom: Secure Storage (Production)

In production, implement KeyshareStorage backed by a secure system such as AWS KMS, HashiCorp Vault, Azure Key Vault, or an encrypted database.

import type { KeyshareStorage } from '@embarkai/core' class VaultKeyshareStorage implements KeyshareStorage { async set(userId: string, keyshare: string): Promise<void> { // Encrypt and store in your vault/KMS await vault.write(`secret/keyshares/${userId}`, { data: keyshare }) } async get(userId: string): Promise<string | null> { try { const result = await vault.read(`secret/keyshares/${userId}`) return result.data.data } catch { return null } } async has(userId: string): Promise<boolean> { return (await this.get(userId)) !== null } async list(): Promise<string[]> { const keys = await vault.list('secret/keyshares/') return keys.data.keys } }

Warning: Never delete keyshares. If a keyshare is lost, the wallet becomes permanently inaccessible and all funds are unrecoverable. Always use redundant, encrypted storage with backups.

Wallet Operations

Create a Wallet

const wallet = await manager.createWallet('unique-wallet-id')

Internally this runs the full DKLS23 DKG protocol (3 rounds) with the TSS service, stores the resulting keyshare, and returns a ServerWallet instance.

Get an Existing Wallet

const wallet = await manager.getWallet('treasury_main') console.log(wallet.ownerAddress) // EOA derived from TSS public key console.log(wallet.smartAccountAddress) // Predicted AA address

List All Wallets

const wallets = await manager.listWallets() for (const w of wallets) { console.log(w.userId, w.smartAccountAddress) }

Check Wallet Existence

const exists = await manager.walletExists('treasury_main') const hasLocal = await manager.hasKeyshare('treasury_main')

Sending Transactions

Single Transaction

const userOpHash = await wallet.sendUserOperation( '0xRecipientAddress', // to '1000000000000000000', // value in wei '0x', // calldata (optional) 'standard', // fee type: 'economy' | 'standard' | 'fast' )

Batch Transaction

Execute multiple operations atomically in a single UserOperation:

const userOpHash = await wallet.sendBatchUserOperation([ { to: '0xAddr1', value: '1000000000000000000', data: '0x' }, { to: '0xAddr2', value: '0', data: '0xabcdef...' }, ], 'standard')

Parallel Operations

Use nonceKey to send multiple UserOperations concurrently without nonce conflicts:

const [hash1, hash2] = await Promise.all([ wallet.sendUserOperation(addr1, value1, '0x', 'standard', 0n), wallet.sendUserOperation(addr2, value2, '0x', 'standard', 1n), ])

Transaction Receipts

// Non-blocking check const receipt = await wallet.getUserOperationReceipt(userOpHash) if (receipt?.success) { console.log('TX:', receipt.transactionHash) } // Blocking wait with polling const receipt = await wallet.waitForUserOperationReceipt(userOpHash, { pollIntervalMs: 2000, // Check every 2 seconds maxPollTimeMs: 120000, // Timeout after 2 minutes })

Receipt fields:

FieldTypeDescription
successbooleanWhether the operation succeeded
transactionHashstringOn-chain transaction hash
blockNumberstringBlock number
blockHashstringBlock hash
actualGasUsedstringGas consumed
actualGasCoststringTotal gas cost in wei
logsany[]Transaction event logs

Signing

Sign arbitrary 32-byte digests using the TSS protocol:

const signature = await wallet.signDigest( '0xabcdef1234567890...', // 32-byte hex hash "m/44'/60'/0'/0/0", // BIP-44 path (optional) ) console.log(signature.r) // hex string console.log(signature.s) // hex string console.log(signature.v) // 27 or 28

Vault Backup

Back up keyshares to EmbarkAI ShareVault with password encryption:

// Backup await manager.backupToVault('treasury_main', 'secure-password') // Check backup exists const { hasBackup } = await manager.checkBackupExists('treasury_main') // Restore to local storage await manager.restoreFromVault('treasury_main', 'secure-password')

Multi-Chain

Create separate manager instances for each chain:

import { createServerWalletManager, MemoryKeyshareStorage } from '@embarkai/core' const storage = new MemoryKeyshareStorage() const lumiaManager = createServerWalletManager({ apiKey: process.env.EMBARK_API_KEY!, chainId: 994873017, // Lumia Mainnet storage, }) const sepoliaManager = createServerWalletManager({ apiKey: process.env.EMBARK_API_KEY!, chainId: 11155111, // Sepolia storage, })

The same wallet (identified by userId) can be used across chains — the TSS key pair is chain-agnostic. Only the smart account address differs per chain.

Security Best Practices

  1. Never commit API keys to version control. Use environment variables or a secrets manager.
  2. Use secure keyshare storage in production (AWS KMS, HashiCorp Vault, etc.).
  3. Never delete keyshares — loss is permanent and unrecoverable.
  4. Use random wallet IDs instead of predictable patterns like user_1.
  5. Implement rate limiting on wallet creation endpoints.
  6. Enable audit logging for all wallet operations.
  7. Rotate API keys periodically via the Dashboard.
Last updated on