npm package: @lumiapassport/core
Description: Framework-agnostic core SDK for Mbark Wallet smart accounts
Author: Lumia
Latest version: 1.17.12

# @lumiapassport/core

Framework-agnostic core SDK for Lumia Passport smart accounts.

## Features

- ✅ **Zero React dependencies** - Works in Node.js, browser, and edge functions
- ✅ **TypeScript-first** - Full type safety
- ✅ **Modular** - Import only what you need
- ✅ **Storage abstraction** - Works with file storage, memory, or custom storage
- ✅ **Account Abstraction** - Full ERC-4337 support
- ✅ **MPC/TSS** - Secure key management
- ✅ **Server-side wallets** - Backend wallet management with API keys
- ✅ **Zero configuration** - Works out of the box with Lumia Beam testnet

## Installation

```bash
npm install @lumiapassport/core viem dkls23-wasm
# or
pnpm add @lumiapassport/core viem dkls23-wasm
# or
yarn add @lumiapassport/core viem dkls23-wasm
```

## Quick Start

The SDK works out of the box with Lumia Beam testnet. No additional configuration required:

```typescript
import { createServerWalletManager, MemoryKeyshareStorage } from '@lumiapassport/core';

// Create manager with just API key
const manager = createServerWalletManager({
  apiKey: process.env.LUMIA_PASSPORT_API_KEY!, // Get from dashboard.lumiapassport.com
  storage: new MemoryKeyshareStorage(),
});

// Create a wallet
const wallet = await manager.createWallet('my-wallet');
console.log('Smart Account address:', wallet.smartAccountAddress);
```

## Usage

### Server-Side Wallets (Recommended)

Create and manage backend wallets with a simple high-level API:

```typescript
import { createServerWalletManager, MemoryKeyshareStorage } from '@lumiapassport/core';

// Create manager
const manager = createServerWalletManager({
  apiKey: process.env.LUMIA_PASSPORT_API_KEY!, // Get from dashboard.lumiapassport.com
  storage: new MemoryKeyshareStorage(), // Use FileKeyshareStorage or custom storage in production
});

// Create a wallet (handles all TSS protocol complexity)
const wallet = await manager.createWallet('treasury_main');
console.log('Owner address (EOA):', wallet.ownerAddress);
console.log('Smart Account address:', wallet.smartAccountAddress);

// Send a transaction (Account Abstraction with ERC-4337)
const userOpHash = await wallet.sendUserOperation(
  '0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb', // recipient
  '1000000000000000000', // 1 ETH in wei
  '0x', // optional contract call data
  'standard' // fee type: 'economy' | 'standard' | 'fast'
);
console.log('UserOp hash:', userOpHash);

// Wait for transaction confirmation
const receipt = await manager.waitForUserOperationReceipt(userOpHash);
console.log('Transaction hash:', receipt.transactionHash);

// Sign a message
const signature = await wallet.signDigest(messageHash);

// List all wallets
const wallets = await manager.listWallets();
wallets.forEach(w => {
  console.log(`${w.userId}: ${w.smartAccountAddress}`);
});
```

### Storage Options

#### MemoryKeyshareStorage (Development only)

Simple in-memory storage. **Keyshares are lost on restart!**

```typescript
import { MemoryKeyshareStorage } from '@lumiapassport/core';

const storage = new MemoryKeyshareStorage();
```

#### FileKeyshareStorage (Production-ready)

Encrypted file-based storage using AES-256-CBC. Available in Node.js only:

```typescript
import { FileKeyshareStorage } from '@lumiapassport/core/clients/node';

const storage = new FileKeyshareStorage({
  storageDir: './data/keyshares',
  encryptionPassword: process.env.LUMIA_PASSPORT_KEYSHARE_ENCRYPTION_PASSWORD!,
});

// Initialize storage directory
await storage.init();

const manager = createServerWalletManager({
  apiKey: process.env.LUMIA_PASSPORT_API_KEY!,
  storage,
});
```

#### Custom Storage

Implement the `KeyshareStorage` interface for custom backends (AWS KMS, Vault, etc.):

```typescript
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[];
}
```

### Keyshare Backup and Restore

Backup keyshares to Lumia ShareVault for recovery:

```typescript
// Check if wallet exists on TSS service
const exists = await manager.walletExists('my-wallet');

// Check if we have keyshare locally
const hasKeyshare = await manager.hasKeyshare('my-wallet');

if (!hasKeyshare) {
  // Check if backup exists in vault
  const { hasBackup } = await manager.checkBackupExists('my-wallet');

  if (hasBackup) {
    // Restore from vault
    await manager.restoreFromVault('my-wallet', process.env.BACKUP_PASSWORD!);
    console.log('Keyshare restored from vault');
  }
}

// Backup keyshare to vault
await manager.backupToVault('my-wallet', process.env.BACKUP_PASSWORD!);
```

### Using Paymaster (Sponsored Transactions)

Configure paymaster for gasless transactions:

```typescript
const manager = createServerWalletManager({
  apiKey: process.env.LUMIA_PASSPORT_API_KEY!,
  storage,
  paymasterAddress: '0x...' as `0x${string}`, // Your paymaster contract
});
```

**See [Server Wallets Documentation](../../docs/SERVER_WALLETS.md) for complete guide.**

### Smart Contract Interactions

The SDK provides utilities for encoding smart contract calls for use with `sendUserOperation()`.

#### ERC20 Token Transfer

```typescript
import {
  createServerWalletManager,
  MemoryKeyshareStorage,
  encodeERC20Transfer,
} from '@lumiapassport/core';

const manager = createServerWalletManager({
  apiKey: process.env.LUMIA_PASSPORT_API_KEY!,
  storage: new MemoryKeyshareStorage(),
});

const wallet = await manager.getWallet('treasury_main');

// Encode ERC20 transfer call
const callData = await encodeERC20Transfer(
  '0xRecipientAddress...',
  1000n * 10n ** 18n  // 1000 tokens (18 decimals)
);

// Send the transaction
const userOpHash = await wallet.sendUserOperation(
  '0xTokenContractAddress...',  // to = token contract
  '0',                          // value = 0 (no native token)
  callData,                     // encoded transfer call
  'standard'
);

// Wait for confirmation
const receipt = await manager.waitForUserOperationReceipt(userOpHash);
console.log('Transaction hash:', receipt.transactionHash);
```

#### ERC20 Approve + TransferFrom Pattern

```typescript
import { encodeERC20Approve } from '@lumiapassport/core';

// Step 1: Approve DEX router to spend tokens
const approveData = await encodeERC20Approve(
  '0xDEXRouterAddress...',
  2n ** 256n - 1n  // MaxUint256 for unlimited approval
);

await wallet.sendUserOperation(
  '0xTokenContractAddress...',
  '0',
  approveData,
  'standard'
);
```

#### Arbitrary Contract Call

For any smart contract method, use `encodeContractCall()`:

```typescript
import { encodeContractCall } from '@lumiapassport/core';

// Define ABI for the function you want to call
const stakingAbi = [
  {
    type: 'function',
    name: 'stake',
    stateMutability: 'nonpayable',
    inputs: [
      { name: 'amount', type: 'uint256' },
      { name: 'lockPeriod', type: 'uint256' },
    ],
    outputs: [],
  },
] as const;

// Encode the call
const callData = await encodeContractCall(
  stakingAbi,
  'stake',
  [1000n * 10n ** 18n, 30n * 24n * 60n * 60n]  // amount, lock period in seconds
);

// Send transaction
await wallet.sendUserOperation(
  '0xStakingContractAddress...',
  '0',
  callData,
  'standard'
);
```

#### Payable Functions (Sending Native Token + Data)

```typescript
import { encodeContractCall } from '@lumiapassport/core';

// Example: Wrap ETH to WETH
const wethAbi = [
  {
    type: 'function',
    name: 'deposit',
    stateMutability: 'payable',
    inputs: [],
    outputs: [],
  },
] as const;

const callData = await encodeContractCall(wethAbi, 'deposit', []);

await wallet.sendUserOperation(
  '0xWETHContractAddress...',
  '1000000000000000000',  // 1 ETH in wei
  callData,
  'standard'
);
```

#### NFT Transfers (ERC721 / ERC1155)

```typescript
import {
  encodeERC721TransferFrom,
  encodeERC721SafeTransferFrom,
  encodeERC1155SafeTransferFrom,
} from '@lumiapassport/core';

// ERC721 transfer
const nftData = await encodeERC721TransferFrom(
  wallet.smartAccountAddress,  // from
  '0xRecipientAddress...',     // to
  123n                          // tokenId
);

await wallet.sendUserOperation('0xNFTContract...', '0', nftData, 'standard');

// ERC1155 transfer
const multiTokenData = await encodeERC1155SafeTransferFrom(
  wallet.smartAccountAddress,
  '0xRecipientAddress...',
  1n,      // tokenId
  100n,    // amount
  '0x'     // data
);

await wallet.sendUserOperation('0xERC1155Contract...', '0', multiTokenData, 'standard');
```

#### Available Encoding Helpers

| Function | Description |
|----------|-------------|
| `encodeContractCall(abi, functionName, args)` | Generic encoder for any contract |
| `encodeERC20Transfer(to, amount)` | ERC20 transfer |
| `encodeERC20Approve(spender, amount)` | ERC20 approve |
| `encodeERC20TransferFrom(from, to, amount)` | ERC20 transferFrom |
| `encodeERC721TransferFrom(from, to, tokenId)` | ERC721 transfer |
| `encodeERC721SafeTransferFrom(from, to, tokenId, data?)` | ERC721 safe transfer |
| `encodeERC721Approve(to, tokenId)` | ERC721 approve |
| `encodeERC721SetApprovalForAll(operator, approved)` | ERC721 approval for all |
| `encodeERC1155SafeTransferFrom(from, to, id, amount, data)` | ERC1155 transfer |
| `encodeERC1155SafeBatchTransferFrom(from, to, ids, amounts, data)` | ERC1155 batch transfer |
| `encodeERC1155SetApprovalForAll(operator, approved)` | ERC1155 approval for all |

#### Standard ABIs

The SDK exports standard ABIs for common token standards:

```typescript
import { ERC20_ABI, ERC721_ABI, ERC1155_ABI } from '@lumiapassport/core';

// Use with viem directly or with encodeContractCall
```

### Blockchain Read Layer

Read blockchain data without wallet connection:

```typescript
import { getPublicClient } from '@lumiapassport/core/read';
import { erc20Abi } from 'viem';

// Create client for Lumia mainnet (uses built-in defaults)
const client = getPublicClient({ chainId: 994873017 });

// Read contract data
const balance = await client.readContract({
  address: '0x...tokenAddress',
  abi: erc20Abi,
  functionName: 'balanceOf',
  args: ['0x...userAddress'],
});

// Batch multiple reads into single RPC call
const results = await client.multicall({
  contracts: [
    { address: '0x...token1', abi: erc20Abi, functionName: 'balanceOf', args: ['0x...user'] },
    { address: '0x...token2', abi: erc20Abi, functionName: 'symbol' },
  ],
});

// Query event logs
const logs = await client.getLogs({
  address: '0x...tokenAddress',
  event: { type: 'event', name: 'Transfer', inputs: [...] },
  fromBlock: 1000000n,
  toBlock: 'latest',
});

// Get transaction receipt (returns null if pending)
const receipt = await client.getTransactionReceipt({
  hash: '0x...transactionHash',
});

// Wait for transaction to be mined
const receipt = await client.waitForTransactionReceipt({
  hash: '0x...transactionHash',
  confirmations: 1,      // optional, default: 1
  pollingInterval: 4000, // optional, default: 4000ms
  timeout: 60000,        // optional, default: 60000ms
  onReplaced: (response) => {
    console.log('Transaction replaced:', response.reason);
  },
});
```

**Features:**
- **LRU Caching** - 12s TTL with 1000 max entries
- **RPC Fallback** - Automatic failover to backup RPCs
- **Multicall Batching** - Batch reads for better performance
- **Log Pagination** - Automatic chunking for large block ranges
- **Transaction Receipts** - Get receipt or wait with polling/timeout

### Bundler Operations (Low-Level API)

For direct bundler interactions (e.g., backend services), use these low-level functions:

#### Supported Networks

| Network | Chain ID | Bundler URL |
|---------|----------|-------------|
| Lumia Testnet (Beam) | 2030232745 | https://api.lumiapassport.com/rundler |
| Lumia Mainnet (Prism) | 994873017 | https://api.lumiapassport.com/rundler-prism |
| Sepolia | 11155111 | - |
| BSC Testnet | 97 | - |
| Arbitrum Sepolia | 421614 | - |
| Base Sepolia | 84532 | - |

#### Send UserOperation

```typescript
import { sendUserOperationRaw, getUserOperationReceipt } from '@lumiapassport/core';
import { getChainConfig } from '@lumiapassport/core/read';

const CHAIN_ID = 2030232745; // Lumia Testnet
const chainConfig = getChainConfig(CHAIN_ID);

if (!chainConfig) {
  throw new Error(`Chain ${CHAIN_ID} is not supported`);
}

// Submit UserOperation to bundler
const userOpHash = await sendUserOperationRaw(userOp, { chainId: CHAIN_ID });

// Get receipt (returns null if pending)
const receipt = await getUserOperationReceipt(userOpHash, chainConfig.bundlerUrl);
```

#### Function Signatures

```typescript
// Submit a signed UserOperation to the bundler
sendUserOperationRaw(
  userOp: UserOperationV07,
  options: { chainId: number }
): Promise<`0x${string}`>

// Submit with automatic retry on nonce/gas errors
sendUserOperationWithRetry(
  userOp: UserOperationV07,
  resigner: (nonce: `0x${string}`) => Promise<UserOperationV07>,
  options: { chainId: number; maxRetries: number }
): Promise<`0x${string}`>

// Get receipt for a submitted UserOperation
getUserOperationReceipt(
  userOpHash: `0x${string}`,
  bundlerUrl: string
): Promise<any>
```

#### Get Chain Configuration

```typescript
import { getChainConfig, isChainSupported, LUMIA_TESTNET_CHAIN_ID } from '@lumiapassport/core/read';

// Check if chain is supported
if (isChainSupported(chainId)) {
  const config = getChainConfig(chainId);
  console.log('Bundler URL:', config.bundlerUrl);
  console.log('RPC URL:', config.rpcUrls[0]);
  console.log('Explorer:', config.blockExplorerUrl);
}
```

## API

### Modules

- **read** - Blockchain read operations (import from `@lumiapassport/core/read`)
- **bundler** - UserOperation management
- **auth** - JWT token management and verification
- **clients** - HTTP clients and account management
- **mpc** - MPC/TSS operations
- **utils** - Helper utilities

### Storage Adapters

- `MemoryStorage` - In-memory storage (server)
- `LocalStorageAdapter` - Browser localStorage
- Custom adapters via `TokenStorage` interface

## Examples

See [USAGE_EXAMPLES.md](../../USAGE_EXAMPLES.md) for comprehensive examples.

## License

MIT