smart-account-kit
v0.2.5
Published
TypeScript SDK for deploying and managing OpenZeppelin Smart Account contracts on Stellar/Soroban with WebAuthn passkey authentication
Downloads
762
Maintainers
Readme
Smart Account Kit
TypeScript SDK for deploying and managing OpenZeppelin Smart Account contracts on Stellar/Soroban with WebAuthn passkey authentication.
Features
- Passkey Authentication: Create and manage smart wallets secured by WebAuthn passkeys
- Session Management: Automatic session persistence for seamless reconnection
- Multiple Signer Types: Support for passkeys (secp256r1), Ed25519 keys, and policy signers
- Context Rules: Fine-grained authorization control for different operations
- Policy Support: Threshold multisig, spending limits, and custom policies
- Storage Adapters: Flexible credential storage (IndexedDB, localStorage, custom)
Installation
pnpm add smart-account-kitQuick Start
import { SmartAccountKit, IndexedDBStorage } from 'smart-account-kit';
// Initialize the SDK
const kit = new SmartAccountKit({
rpcUrl: 'https://soroban-testnet.stellar.org',
networkPassphrase: 'Test SDF Network ; September 2015',
accountWasmHash: 'YOUR_ACCOUNT_WASM_HASH',
webauthnVerifierAddress: 'CWEBAUTHN_VERIFIER_ADDRESS',
storage: new IndexedDBStorage(),
});
// On page load - silent restore from stored session
const result = await kit.connectWallet();
if (!result) {
// No stored session, show connect button
showConnectButton();
}
// User clicks "Create Wallet"
const { contractId, credentialId } = await kit.createWallet('My App', '[email protected]', {
autoSubmit: true,
});
// User clicks "Connect Wallet" - prompts for passkey selection
await kit.connectWallet({ prompt: true });
// Sign and submit a transaction
const result = await kit.signAndSubmit(transaction);Configuration
SmartAccountKit Options
| Option | Type | Required | Description |
|--------|------|----------|-------------|
| rpcUrl | string | Yes | Stellar RPC URL |
| networkPassphrase | string | Yes | Network passphrase |
| accountWasmHash | string | Yes | Smart account WASM hash |
| webauthnVerifierAddress | string | Yes | WebAuthn verifier contract address |
| timeoutInSeconds | number | No | Transaction timeout (default: 30) |
| storage | StorageAdapter | No | Credential storage adapter |
| rpId | string | No | WebAuthn relying party ID |
| rpName | string | No | WebAuthn relying party name |
| launchtube | LaunchtubeConfig | No | Launchtube fee sponsoring config |
Launchtube Fee Sponsoring
Launchtube is a service that sponsors transaction fees, enabling gasless transactions for users. When configured, all transactions are submitted via Launchtube by default.
const kit = new SmartAccountKit({
// ... other config
launchtube: {
url: 'https://launchtube.xyz',
jwt: 'your-jwt-token', // Optional if handled server-side
},
});
// All operations now use Launchtube by default
await kit.transfer(tokenContract, recipient, amount);
// To bypass Launchtube for specific operations, use skipLaunchtube
await kit.transfer(tokenContract, recipient, amount, { skipLaunchtube: true });
// Check Launchtube credits
if (kit.launchtube) {
const info = await kit.launchtube.getInfo();
console.log('Credits remaining:', info?.credits);
}Storage Adapters
import {
IndexedDBStorage, // Recommended for web apps
LocalStorageAdapter, // Simple fallback
MemoryStorage, // For testing
} from 'smart-account-kit';
// Use IndexedDB (recommended)
const storage = new IndexedDBStorage();
// Or implement your own
class MyStorage implements StorageAdapter {
async save(credential: StoredCredential): Promise<void> { ... }
async get(credentialId: string): Promise<StoredCredential | null> { ... }
async saveSession(session: StoredSession): Promise<void> { ... }
async getSession(): Promise<StoredSession | null> { ... }
// ... other methods
}API Reference
SmartAccountKit
The main SDK client class.
import { SmartAccountKit } from 'smart-account-kit';Core Methods
| Method | Description |
|--------|-------------|
| constructor(config: SmartAccountConfig) | Initialize SDK with configuration |
| createWallet(appName, userName, options?) | Create new smart wallet with passkey |
| connectWallet(options?) | Connect to existing wallet |
| disconnect() | Disconnect and clear session |
| authenticatePasskey() | Authenticate with passkey without connecting |
| discoverContractsByCredential(credentialId) | Find contracts by credential ID via indexer |
| discoverContractsByAddress(address) | Find contracts by G/C-address via indexer |
| sign(transaction, options?) | Sign auth entries (use signAndSubmit instead) |
| signAndSubmit(transaction, options?) | Sign, re-simulate, and submit (recommended) |
| signAuthEntry(authEntry, options?) | Sign a single auth entry |
| fundWallet(nativeTokenContract) | Fund via Friendbot (testnet) |
| transfer(tokenContract, recipient, amount) | Direct token transfer |
| getContractDetailsFromIndexer(contractId) | Get contract details from indexer |
| convertPolicyParams(params) | Convert policy params to ScVal |
| buildPoliciesScVal(policies) | Build policies ScVal for context rules |
Sub-Manager Properties
| Property | Type | Description |
|----------|------|-------------|
| kit.signers | SignerManager | Manage signers on rules |
| kit.rules | ContextRuleManager | CRUD for context rules |
| kit.policies | PolicyManager | Manage policies on rules |
| kit.credentials | CredentialManager | Credential lifecycle |
| kit.multiSigners | MultiSignerManager | Multi-signer flows |
| kit.externalSigners | ExternalSignerManager | G-address signers |
| kit.indexer | IndexerClient \| null | Indexer client for contract discovery |
| kit.events | SmartAccountEventEmitter | Event subscription |
Usage Examples
// Create a new wallet
const { contractId, credentialId } = await kit.createWallet('My App', '[email protected]', {
autoSubmit: true, // Automatically deploy the wallet
autoFund: true, // Fund via Friendbot (testnet only)
nativeTokenContract: 'CDLZFC3...',
});
// Connect to existing wallet
const result = await kit.connectWallet(); // Silent restore from session
await kit.connectWallet({ prompt: true }); // Prompt user to select passkey
await kit.connectWallet({ fresh: true }); // Ignore session, always prompt
await kit.connectWallet({ credentialId: '...' }); // Connect with specific credential
await kit.connectWallet({ contractId: 'C...' }); // Connect with specific contract
// Transfer tokens
const result = await kit.transfer('CTOKEN...', 'GRECIPIENT...', '100');
// Disconnect
await kit.disconnect();Sub-Managers
SignerManager (kit.signers)
Manage signers on context rules.
| Method | Description |
|--------|-------------|
| addPasskey(contextRuleId, appName, userName, options?) | Add passkey signer |
| addDelegated(contextRuleId, address) | Add G-address signer |
| remove(contextRuleId, signer) | Remove a signer |
| removePasskey(contextRuleId, credentialId) | Remove passkey by credential ID |
// Add a new passkey signer
const { credentialId, transaction } = await kit.signers.addPasskey(
0, // Context rule ID
'My App', // App name
'Recovery Key', // User name
{ nickname: 'Backup YubiKey' }
);
// Add a delegated signer (Stellar account)
await kit.signers.addDelegated(0, 'GABC...');
// Remove a signer
await kit.signers.remove(0, signer);
await kit.signers.removePasskey(0, 'credential-id');ContextRuleManager (kit.rules)
Manage context rules.
| Method | Description |
|--------|-------------|
| add(contextType, name, signers, policies) | Create rule |
| get(contextRuleId) | Get single rule |
| getAll(contextRuleType) | Get all rules of type |
| remove(contextRuleId) | Delete rule |
| updateName(contextRuleId, name) | Update rule name |
| updateExpiration(contextRuleId, ledger) | Update expiration ledger |
// Add a new context rule
await kit.rules.add(contextType, 'Rule Name', signers, policies);
// Get context rules
const rule = await kit.rules.get(0);
const allRules = await kit.rules.getAll(contextType);
// Update rules
await kit.rules.updateName(0, 'New Name');
await kit.rules.updateExpiration(0, expirationLedger);
// Remove a rule
await kit.rules.remove(0);PolicyManager (kit.policies)
Manage policies on context rules.
| Method | Description |
|--------|-------------|
| add(contextRuleId, policyAddress, installParams) | Add policy to rule |
| remove(contextRuleId, policyAddress) | Remove policy from rule |
// Add a policy
await kit.policies.add(0, 'CPOLICY...', installParams);
// Remove a policy
await kit.policies.remove(0, 'CPOLICY...');CredentialManager (kit.credentials)
Manage stored credentials.
| Method | Description |
|--------|-------------|
| getAll() | Get all stored credentials |
| getForWallet() | Get credentials for current wallet |
| getPending() | Get pending deployments |
| create(options?) | Create new credential |
| save(credential) | Save credential to storage |
| deploy(credentialId, options?) | Deploy pending credential |
| markDeployed(credentialId) | Mark as deployed |
| markFailed(credentialId, error?) | Mark as failed |
| sync(credentialId) | Sync with on-chain state |
| syncAll() | Sync all credentials |
| delete(credentialId) | Delete credential |
// Get credentials
const all = await kit.credentials.getAll();
const pending = await kit.credentials.getPending();
const walletCreds = await kit.credentials.getForWallet();
// Deploy a pending credential
const result = await kit.credentials.deploy('credential-id', { autoSubmit: true });
// Sync with on-chain state
await kit.credentials.syncAll();
// Delete a pending credential
await kit.credentials.delete('credential-id');MultiSignerManager (kit.multiSigners)
Multi-signer transaction flows.
| Method | Description |
|--------|-------------|
| transfer(tokenContract, recipient, amount, selectedSigners) | Multi-sig transfer |
| getAvailableSigners() | Get all signers from rules |
| extractCredentialId(signer) | Get credential ID from signer |
| signerMatchesCredential(signer, credentialId) | Check if signer matches credential |
| signerMatchesAddress(signer, address) | Check if signer matches address |
| needsMultiSigner(signers) | Check if multi-sig is needed |
| buildSelectedSigners(signers, activeCredentialId?) | Build signer selection |
| operation(assembledTx, selectedSigners, options?) | Execute generic multi-sig operation |
// Get all available signers
const signers = await kit.multiSigners.getAvailableSigners();
// Check if multi-sig is needed
if (kit.multiSigners.needsMultiSigner(signers)) {
// Build selected signers for transaction
const selected = kit.multiSigners.buildSelectedSigners(signers, activeCredentialId);
// Execute multi-sig transfer
const result = await kit.multiSigners.transfer(
'CTOKEN...',
'GRECIPIENT...',
'100',
selected
);
}External Signer Support
import { ExternalSignerManager } from 'smart-account-kit';ExternalSignerManager (kit.externalSigners)
Manage G-address (delegated) signers.
| Method | Description |
|--------|-------------|
| addFromSecret(secretKey) | Add keypair signer (memory only) |
| addFromWallet(adapter) | Connect external wallet |
| restoreConnections() | Restore persisted wallet connections |
| canSignFor(address) | Check if can sign for address |
| signAuthEntry(address, authEntry) | Sign auth entry for address |
| getAll() | List all external signers |
| remove(address) | Remove signer |
// Add a keypair signer
kit.externalSigners.addFromSecret('SXXX...');
// Connect external wallet
await kit.externalSigners.addFromWallet(walletAdapter);
// Check signing capability
if (kit.externalSigners.canSignFor('GABC...')) {
const signedEntry = await kit.externalSigners.signAuthEntry('GABC...', authEntry);
}Types
Configuration Types
import type {
SmartAccountConfig, // SDK initialization config
PolicyConfig, // Policy contract config
LaunchtubeConfig, // Launchtube fee sponsoring config
SubmissionOptions, // Transaction submission options
} from 'smart-account-kit';Credential & Session Types
import type {
StoredCredential, // Full credential metadata
StoredSession, // Auto-reconnect session data
CredentialDeploymentStatus, // "pending" | "failed"
StorageAdapter, // Storage backend interface
} from 'smart-account-kit';Result Types
import type {
CreateWalletResult, // Wallet creation outcome
ConnectWalletResult, // Connection outcome
TransactionResult, // Transaction outcome (success/failure)
} from 'smart-account-kit';External Wallet Types
import type {
ExternalWalletAdapter, // Interface for wallet extensions
ConnectedWallet, // Single wallet connection info
SelectedSigner, // Signer selection for multi-sig
} from 'smart-account-kit';Contract Types (from smart-account bindings)
import type {
ContractSigner, // On-chain signer type (alias: Signer)
ContractSignerId, // Signer ID type
ContextRule, // Context rule structure
ContextRuleType, // Rule type enum
ContextRuleMeta, // Rule metadata (alias: Meta)
WebAuthnSigData, // WebAuthn signature format
Signatures, // Signature map type
SimpleThresholdAccountParams, // M-of-N multisig params
WeightedThresholdAccountParams, // Weighted voting params
SpendingLimitAccountParams, // Time-limited spending params
} from 'smart-account-kit';Builder Functions
Signer Builders
import {
createDelegatedSigner, // Create Stellar account signer (G-address)
createExternalSigner, // Create custom verifier signer
createWebAuthnSigner, // Create passkey signer
createEd25519Signer, // Create Ed25519 signer
} from 'smart-account-kit';
// Create a delegated signer
const signer = createDelegatedSigner('GABC...', 'ed25519-verifier-address');
// Create a WebAuthn signer
const passkeySigner = createWebAuthnSigner(verifierAddress, publicKey, credentialId);Context Rule Type Builders
import {
createDefaultContext, // Default rule (matches any operation)
createCallContractContext, // Rule for specific contract calls
createCreateContractContext, // Rule for contract deployments
} from 'smart-account-kit';
// Create a context for calling a specific contract
const context = createCallContractContext('CCONTRACT...');Policy Parameter Builders
import {
createThresholdParams, // M-of-N multisig
createWeightedThresholdParams, // Weighted voting
createSpendingLimitParams, // Time-limited spending
LEDGERS_PER_HOUR, // ~720 ledgers
LEDGERS_PER_DAY, // ~17,280 ledgers
LEDGERS_PER_WEEK, // ~120,960 ledgers
} from 'smart-account-kit';
// Create 2-of-3 multisig params
const thresholdParams = createThresholdParams(2);
// Create spending limit params
const spendingParams = createSpendingLimitParams(
'CTOKEN...', // Token contract
BigInt(1000 * 10_000_000), // 1000 tokens in stroops
LEDGERS_PER_DAY // Reset period
);Signer Helper Functions
import {
getCredentialIdFromSigner, // Extract credential ID from signer
describeSignerType, // Human-readable signer type
formatSignerForDisplay, // UI-friendly signer display
signersEqual, // Compare two signers
getSignerKey, // Unique signer identifier
collectUniqueSigners, // Deduplicate signers
} from 'smart-account-kit';Display Helpers
import {
truncateAddress, // Truncate address for UI (e.g., "GABC...XYZ")
formatContextType, // Human-readable context type
} from 'smart-account-kit';Utility Functions
import {
// Conversion
xlmToStroops, // Convert XLM to stroops
stroopsToXlm, // Convert stroops to XLM
// Validation
validateAddress, // Validate Stellar address (G... or C...)
validateAmount, // Validate positive amount
validateNotEmpty, // Validate non-empty string
} from 'smart-account-kit';Constants
import {
WEBAUTHN_TIMEOUT_MS, // WebAuthn timeout (60000ms)
BASE_FEE, // Transaction base fee
STROOPS_PER_XLM, // 10,000,000
FRIENDBOT_RESERVE_XLM, // Friendbot reserve amount
} from 'smart-account-kit';Error Classes
import {
SmartAccountError, // Base error class
SmartAccountErrorCode, // Error codes enum
WalletNotConnectedError, // No wallet connected
CredentialNotFoundError, // Credential not found in storage
SignerNotFoundError, // Signer not found on-chain
SimulationError, // Transaction simulation failed
SubmissionError, // Transaction submission failed
ValidationError, // Input validation failed
WebAuthnError, // WebAuthn operation failed
SessionError, // Session management error
wrapError, // Error wrapper utility
} from 'smart-account-kit';
// Error handling
try {
await kit.transfer(...);
} catch (error) {
if (error instanceof WalletNotConnectedError) {
// Handle not connected
} else if (error instanceof SimulationError) {
// Handle simulation failure
}
}Event System
import { SmartAccountEventEmitter } from 'smart-account-kit';
import type { SmartAccountEventMap, SmartAccountEvent, EventListener } from 'smart-account-kit';Available Events
| Event | Description |
|-------|-------------|
| walletConnected | When connected to wallet |
| walletDisconnected | When disconnected |
| credentialCreated | When passkey registered |
| credentialDeleted | When credential removed |
| sessionExpired | When session expires |
| transactionSigned | When auth entries signed |
| transactionSubmitted | When tx submitted |
// Listen to events
kit.events.on('walletConnected', ({ contractId }) => {
console.log('Connected to:', contractId);
});
kit.events.on('transactionSubmitted', ({ hash, success }) => {
console.log('Transaction:', hash, success ? 'succeeded' : 'failed');
});
// One-time listener
kit.events.once('walletConnected', handler);
// Remove listener
kit.events.off('walletConnected', handler);Wallet Adapters
import { StellarWalletsKitAdapter } from 'smart-account-kit';
import type { StellarWalletsKitAdapterConfig } from 'smart-account-kit';
// Create adapter for StellarWalletsKit integration
const adapter = new StellarWalletsKitAdapter({
kit: stellarWalletsKit,
onConnectionChange: (connected) => {
console.log('Wallet connection changed:', connected);
},
});
// Use with external signers
await kit.externalSigners.addFromWallet(adapter);Launchtube Client
The SDK includes a Launchtube client for fee-sponsored transaction submission.
import {
LaunchtubeClient,
} from 'smart-account-kit';
import type {
LaunchtubeResponse,
LaunchtubeSendOptions,
} from 'smart-account-kit';Using via SmartAccountKit (Recommended)
When Launchtube is configured in SmartAccountKit, it's used automatically for all transaction submissions:
const kit = new SmartAccountKit({
// ... other config
launchtube: {
url: 'https://launchtube.xyz',
jwt: 'your-jwt-token', // Optional
},
});
// Transactions automatically use Launchtube
await kit.transfer(tokenContract, recipient, amount);
// Bypass Launchtube for specific operations
await kit.transfer(tokenContract, recipient, amount, { skipLaunchtube: true });
// Access the Launchtube client directly
if (kit.launchtube) {
const info = await kit.launchtube.getInfo();
}Using LaunchtubeClient Directly
const launchtube = new LaunchtubeClient({
url: 'https://launchtube.xyz',
jwt: 'your-jwt-token',
});
// Submit a transaction for fee sponsoring
const result = await launchtube.send(signedTransaction, {
fee: 1000000, // Optional: max fee in stroops
});
if (result.success) {
console.log('Transaction hash:', result.hash);
} else {
console.error('Failed:', result.error);
}
// Check remaining credits
const info = await launchtube.getInfo();
console.log('Credits:', info?.credits);Indexer Client
The SDK includes an indexer client for reverse lookups from signer credentials to smart account contracts.
import {
IndexerClient,
IndexerError,
DEFAULT_INDEXER_URLS,
} from 'smart-account-kit';
import type {
IndexerConfig,
IndexedContractSummary,
IndexedSigner,
IndexedPolicy,
IndexedContextRule,
CredentialLookupResponse,
AddressLookupResponse,
ContractDetailsResponse,
IndexerStatsResponse,
} from 'smart-account-kit';Using via SmartAccountKit (Recommended)
// Indexer is auto-configured for testnet
const kit = new SmartAccountKit({ /* config */ });
// Discover contracts by credential ID
const contracts = await kit.discoverContractsByCredential(credentialId);
// Discover contracts by address
const contracts = await kit.discoverContractsByAddress('GABC...');
// Get contract details
const details = await kit.getContractDetailsFromIndexer('CABC...');
// Or use the indexer client directly
if (kit.indexer) {
const stats = await kit.indexer.getStats();
const healthy = await kit.indexer.isHealthy();
}Using IndexerClient Directly
// Create client for a specific network
const indexer = IndexerClient.forNetwork('Test SDF Network ; September 2015');
// Or with custom URL
const indexer = new IndexerClient({
baseUrl: 'https://smart-account-indexer.sdf-ecosystem.workers.dev',
timeout: 10000,
});
// Lookup by credential ID
const { contracts } = await indexer.lookupByCredentialId(credentialIdHex);
// Lookup by address
const { contracts } = await indexer.lookupByAddress('GABC...');
// Get full contract details
const details = await indexer.getContractDetails('CABC...');Re-exported Types
import type { AssembledTransaction } from 'smart-account-kit';Building from Source
Prerequisites
- Node.js >= 20
- pnpm (
npm install -g pnpm) - Stellar CLI (installation guide)
Setup
# Clone the repository
git clone https://github.com/kalepail/smart-account-kit
cd smart-account-kit
# Configure demo environment (has testnet defaults)
cp demo/.env.example demo/.env
# Edit demo/.env if needed
# Install dependencies
pnpm install
# Build everything (generates bindings from network, builds packages)
pnpm run build:allEnvironment Configuration
The build script reads configuration from demo/.env to generate TypeScript bindings by fetching contract metadata from the Stellar network. The demo comes pre-configured with testnet contract addresses.
Key variables in demo/.env:
VITE_RPC_URL- Stellar RPC endpointVITE_NETWORK_PASSPHRASE- Network passphraseVITE_ACCOUNT_WASM_HASH- Smart account contract WASM hash
Getting Contract WASM Hashes
The Smart Account Kit uses contracts from OpenZeppelin's stellar-contracts. You can:
Use pre-deployed testnet contracts (recommended for development):
- The
demo/.env.exampleincludes testnet WASM hashes that are ready to use
- The
Deploy your own contracts:
- Clone stellar-contracts
- Build and deploy the contracts
- Use the resulting WASM hashes or contract IDs
Build Commands
| Command | Description |
|---------|-------------|
| pnpm run build | Build SDK only (requires bindings already generated) |
| pnpm run build:all | Full build: generate bindings from network + build SDK |
| pnpm run build:demo | Build SDK and demo application |
| pnpm run build:watch | Watch mode for SDK development |
| pnpm run test | Run tests |
| pnpm run clean | Remove build artifacts |
Publishing
# Ensure you're logged in to npm
npm login
# Bump version (updates package.json)
pnpm version patch # or minor, major
# Publish (runs build:all automatically via prepublishOnly)
pnpm publish
# Or publish with specific tag
pnpm publish --tag betaNote: The prepublishOnly script automatically runs pnpm run build:all before publishing.
Related
- OpenZeppelin stellar-contracts - Smart account contracts this SDK interacts with
- Demo Application - Interactive demo for testing the SDK
- Indexer - Backend service for contract discovery
License
MIT License - see LICENSE file for details.
