Tip
Looking for the latest smart wallet SDK?
This package is the legacy precursor to OpenZeppelin Smart Accounts. For new projects, use smart-account-kit — a comprehensive SDK built on top of the audited OpenZeppelin stellar-contracts library.
Smart Account Kit includes:
- Context rules with fine-grained authorization scopes
- Policy support (threshold multisig, spending limits, custom policies)
- Session management with automatic credential persistence
- External wallet adapter support
- Built-in indexer for contract discovery
See the OpenZeppelin Smart Account package and multisig example for more details.
Warning
Code in this repo is demo material only. It has not been audited. Do not use to hold, protect, or secure anything.
A TypeScript SDK for creating and managing Stellar smart wallets using passkeys. Works with OpenZeppelin Relayer for submitting passkey-signed transactions onchain.
Demo: passkey-kit-demo.pages.dev
pnpm i passkey-kitimport {
PasskeyKit, // Client-side wallet management
PasskeyServer, // Server-side utilities
SACClient, // Stellar Asset Contract helper
PasskeyClient, // Low-level contract client (from passkey-kit-sdk)
SignerKey, // Signer key type constructor
SignerStore, // Storage type enum
type Signer, // Signer type
type SignerLimits // Signer limits type
} from 'passkey-kit'Handles wallet creation, connection, and transaction signing.
const account = new PasskeyKit({
rpcUrl: string, // Stellar RPC URL
networkPassphrase: string, // Network passphrase
walletWasmHash: string, // Smart wallet WASM hash
timeoutInSeconds?: number, // Transaction timeout (default: 30)
WebAuthn?: { // Optional WebAuthn override
startRegistration,
startAuthentication
}
})| Property | Type | Description |
|---|---|---|
keyId |
string | undefined |
Current passkey ID (base64url) |
wallet |
PasskeyClient | undefined |
Connected wallet client |
networkPassphrase |
string |
Network passphrase |
Creates a new passkey and deploys a smart wallet.
const { rawResponse, keyId, keyIdBase64, contractId, signedTx } = await account.createWallet(
'My App', // App name shown in passkey prompt
'[email protected]', // User identifier
{
rpId?: string, // Relying party ID
authenticatorSelection?: AuthenticatorSelectionCriteria
}
)Creates a new passkey without deploying a wallet.
const { rawResponse, keyId, keyIdBase64, publicKey } = await account.createKey(
'My App',
'[email protected]',
{ rpId?: string, authenticatorSelection?: AuthenticatorSelectionCriteria }
)Connects to an existing wallet using a passkey.
const { rawResponse, keyId, keyIdBase64, contractId } = await account.connectWallet({
rpId?: string,
keyId?: string | Uint8Array, // Skip passkey prompt if provided
getContractId?: (keyId: string) => Promise<string | undefined>, // Lookup function
walletPublicKey?: string // For backwards compatibility
})Signs all auth entries for the connected wallet in a transaction.
const signedTxn = await account.sign(
txn, // AssembledTransaction | Tx | string (XDR)
{
rpId?: string,
keyId?: 'any' | string | Uint8Array, // 'any' allows any passkey
keypair?: Keypair, // Sign with Ed25519 instead
policy?: string, // Sign with policy instead
expiration?: number // Ledger expiration
}
)Signs a single authorization entry. Same options as sign().
const signedEntry = await account.signAuthEntry(entry, options)Add, update, or remove signers from the wallet.
// Add signers
await account.addSecp256r1(keyId, publicKey, limits, store, expiration?)
await account.addEd25519(publicKey, limits, store, expiration?)
await account.addPolicy(policy, limits, store, expiration?)
// Update signers
await account.updateSecp256r1(keyId, publicKey, limits, store, expiration?)
await account.updateEd25519(publicKey, limits, store, expiration?)
await account.updatePolicy(policy, limits, store, expiration?)
// Remove signer
await account.remove(signerKey)Parameters:
keyId- Passkey ID (string or Uint8Array)publicKey- Public key (string or Uint8Array for Secp256r1, Stellar public key for Ed25519)policy- Policy contract addresslimits-SignerLimits(see Types below)store-SignerStore.PersistentorSignerStore.Temporaryexpiration- Optional ledger expiration
Server-side utilities for Mercury indexing and OpenZeppelin Relayer.
const server = new PasskeyServer({
rpcUrl?: string,
relayerUrl?: string, // OpenZeppelin Relayer URL
relayerApiKey?: string, // Relayer API key
mercuryProjectName?: string, // Mercury project name
mercuryUrl?: string, // Mercury URL
mercuryJwt?: string, // Mercury JWT (use either JWT or Key)
mercuryKey?: string // Mercury API key
})Get all signers for a wallet from Mercury.
const signers: Signer[] = await server.getSigners('C...')Reverse lookup a wallet address from a signer.
const contractId = await server.getContractId({
keyId?: string, // Passkey ID (Secp256r1)
publicKey?: string, // Ed25519 public key
policy?: string // Policy address
}, index) // If multiple wallets, select by index (default: 0)Submit a transaction via OpenZeppelin Relayer.
const result = await server.send(txn) // AssembledTransaction | Tx | stringHelper for interacting with Stellar Asset Contracts.
const sac = new SACClient({
networkPassphrase: string,
rpcUrl: string
})
const tokenClient = sac.getSACClient('C...') // SAC contract IDSignerKey.Policy(contractAddress) // Policy signer
SignerKey.Ed25519(publicKey) // Ed25519 signer
SignerKey.Secp256r1(keyId) // Passkey signertype SignerLimits = Map<string, SignerKey[] | undefined> | undefined
// Example: Limit signer to specific contract, requires co-signer
const limits = new Map([
['C...contractAddress', [SignerKey.Ed25519('G...')]]
])enum SignerStore {
Persistent = 'Persistent', // Permanent storage
Temporary = 'Temporary' // Expires, cheaper
}type Signer = {
kind: string // 'Secp256r1' | 'Ed25519' | 'Policy'
key: string // Signer identifier
val: string // Public key or empty
expiration: number | null
storage: 'Persistent' | 'Temporary'
limits: string // JSON stringified limits
evicted?: boolean // True if temporary signer was evicted
}To track signers and reverse lookup wallet addresses, deploy the Zephyr program:
cd ./zephyr
cargo install mercury-cli
# Get a JWT from https://test.mercurydata.app
export MERCURY_JWT="<YOUR.MERCURY.JWT>"
# Requires Rust 1.79.0+
mercury-cli --jwt $MERCURY_JWT --local false --mainnet false deployThis library exports TypeScript only to avoid bundling @stellar/stellar-sdk twice. Configure your bundler to transpile it.
Next.js (next.config.mjs):
/** @type {import('next').NextConfig} */
const nextConfig = {
transpilePackages: [
'passkey-kit',
'passkey-factory-sdk',
'passkey-kit-sdk',
'sac-sdk',
]
}
export default nextConfig# Install dependencies
pnpm i
# Build
pnpm run build
# Run demo
cd ./demo && pnpm i && pnpm run startDirectory structure:
./src- TypeScript SDK source./demo- Demo application./contracts- Rust Soroban smart contracts./zephyr- Mercury Zephyr indexer program
Important
If modifying contracts in ./contracts, run the make commands. Update SMART_WALLET_FACTORY and SMART_WALLET_WASM values from make deploy before running make init.
Important
The bindings in ./packages have been heavily modified. When rebuilding, prefer updating only the src/index.ts files in each package.
- Super Peach - Real-world implementation example
- Discord #passkeys - Questions and showcase