UNPKG

stint-signer

Version:

Short-lived, non-custodial session signer using passkeys for Cosmos SDK

248 lines (173 loc) • 7.68 kB
# Stint [![npm version](https://img.shields.io/npm/v/stint-signer.svg)](https://www.npmjs.com/package/stint-signer) [![codecov](https://codecov.io/gh/n2p5/stint/graph/badge.svg)](https://codecov.io/gh/n2p5/stint) [![Known Vulnerabilities](https://snyk.io/test/github/n2p5/stint/badge.svg)](https://snyk.io/test/github/n2p5/stint) **Zero-balance session signers for smooth Web3 UX.** Post, vote, and transact without wallet popups using Passkeys + Cosmos SDK authz + feegrant modules. > **āš ļø EXPERIMENTAL SOFTWARE - TESTNET ONLY** > > **This project is experimental and has NOT undergone a security audit.** Only use on testnets with test tokens that have no real value. Do not use with real funds or in production environments. ## What is Stint? Stint creates **temporary signers** that can perform limited actions on behalf of your main wallet without holding any funds. Perfect for social dApps, games, and frequent interactions. **How it works:** 1. **Create a session signer** using your device's Passkey (fingerprint/Face ID) 2. **Authorize specific actions** (like posting to social networks) with spending limits 3. **Interact seamlessly** - no more wallet popups for every small transaction Your main wallet stays secure, session signers are time-limited, and you can revoke access anytime. ## Why Use Stint? Perfect for apps that need frequent, small transactions: ### Social Media dApps - Post messages without wallet popups - Like/react to content instantly - Comment and interact seamlessly ### Gaming & NFTs - In-game transactions and trades - Achievement claims and rewards - Tournament entries ### DAOs & Governance - Vote on multiple proposals - Delegate voting power - Submit proposals without friction ### Micro-payments - Content tips and donations - Subscription payments - Pay-per-use services ## Installation ```bash npm install stint-signer # or pnpm add stint-signer # or yarn add stint-signer ``` ## Quick Start ```typescript import { newSessionSigner } from 'stint-signer' // 1. Create session signer (triggers Passkey prompt) const sessionSigner = await newSessionSigner({ primaryClient // Your existing SigningStargateClient }) // 2. Define authorized recipient for security const authorizedRecipient = 'atone1dither123...' // Only allow sends to this address // 3. Set up permissions (one-time setup) const setupMessages = sessionSigner.generateDelegationMessages({ sessionExpiration: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24 hours spendLimit: { denom: 'uphoton', amount: '1000000' }, // Max 1 PHOTON gasLimit: { denom: 'uphoton', amount: '500000' }, // 0.5 PHOTON for gas allowedRecipients: [authorizedRecipient] // Restrict to specific address }) await primaryClient.signAndBroadcast( sessionSigner.primaryAddress(), setupMessages, 'auto' ) // 4. Now send transactions instantly! šŸš€ await sessionSigner.execute.send({ toAddress: authorizedRecipient, // Must match allowedRecipients amount: [{ denom: 'uphoton', amount: '100000' }], memo: 'Posted via session signer!' }) ``` **That's it!** No more wallet popups for authorized transactions. ## Live Example šŸŽÆ **[Try the Dither Demo](./examples/dither-post-demo)** - Post to a decentralized social network without wallet popups! The demo shows how to: - Create session signers with WebAuthn Passkeys - Set up permissions in one transaction - Post messages instantly using session signers ## Basic API ### Creating a Session Signer ```typescript import { newSessionSigner } from 'stint-signer' const sessionSigner = await newSessionSigner({ primaryClient, // Your SigningStargateClient saltName?: 'my-app', // Optional: isolate different apps stintWindowHours?: 24, // Optional: key rotation interval (default: 24 hours) usePreviousWindow?: false, // Optional: use previous time window for grace period logger?: consoleLogger, // Optional: enable debug logs keyMode?: 'passkey' // Optional: 'passkey' (default) or 'random' (ephemeral) }) ``` ### Setting Up Permissions ```typescript // Generate permission messages const messages = sessionSigner.generateDelegationMessages({ sessionExpiration: new Date(Date.now() + 24 * 60 * 60 * 1000), // 24 hours spendLimit: { denom: 'uphoton', amount: '1000000' }, // Max spending gasLimit: { denom: 'uphoton', amount: '500000' }, // Gas allowance allowedRecipients: ['atone1...'] // Optional: restrict recipients }) // Sign with your main wallet (one time) await primaryClient.signAndBroadcast(primaryAddress, messages, 'auto') ``` ### Using Session Signers ```typescript // Send tokens instantly (no wallet popup!) await sessionSigner.execute.send({ toAddress: 'atone1recipient...', amount: [{ denom: 'uphoton', amount: '100000' }], memo: 'Instant transaction!' }) // Check permissions const hasPermission = await sessionSigner.hasAuthzGrant() const hasGasAllowance = await sessionSigner.hasFeegrant() ``` ## Learn More šŸ“– **[Complete Guide](./docs/GUIDE.md)** - Advanced usage, security considerations, and detailed examples šŸŽÆ **[Example App](./examples/dither-post-demo)** - Full working demo with Dither social network ## Advanced Configuration ### Window-Based Key Rotation Stint implements automatic key rotation using time-based windows for enhanced security: ```typescript const sessionSigner = await newSessionSigner({ primaryClient, saltName: 'my-app', // Key rotates every 8 hours for high-security apps stintWindowHours: 8, // Use previous window during transitions to avoid interruptions usePreviousWindow: false, }) ``` **Window Configuration Options:** - `stintWindowHours: 1` - Hourly rotation (maximum security) - `stintWindowHours: 8` - 8-hour rotation (high security) - `stintWindowHours: 24` - Daily rotation (default, balanced) - `stintWindowHours: 168` - Weekly rotation (convenience) **Grace Period Usage:** When users might be near a window boundary, use `usePreviousWindow: true` to access the previous time window's keys, ensuring uninterrupted access during transitions. ### Debugging Window Boundaries ```typescript import { getWindowBoundaries } from 'stint-signer' const boundaries = getWindowBoundaries(24) // 24-hour windows console.log('Current window:', boundaries.windowNumber) console.log('Window start:', boundaries.start) console.log('Window end:', boundaries.end) ``` ## Key Mode Options Stint supports two key generation modes: ### Passkey Mode (Default) - Uses device biometrics (fingerprint/Face ID) via WebAuthn - Keys are deterministically derived from Passkey PRF extension - Survives page refreshes within the same time window - Most secure option ### Random Mode (Fallback) - For environments without Passkey support - Generates cryptographically secure random keys - **Keys are ephemeral** - lost on page refresh - Use when Passkeys aren't available ```typescript // Random mode example - keys won't persist const ephemeralSigner = await newSessionSigner({ primaryClient, keyMode: 'random' // Ephemeral keys, lost on refresh }) ``` ## Key Features āœ… **Zero-balance signers** - Session signers never hold funds āœ… **Passkey security** - Uses device biometrics for key derivation (in passkey mode) āœ… **Automatic key rotation** - Time-windowed keys rotate automatically āœ… **Time-limited** - Sessions expire automatically āœ… **Revocable** - Cancel permissions anytime āœ… **Scoped permissions** - Limit spending, recipients, and actions āœ… **Multi-wallet support** - Works with Keplr, Leap, Cosmostation ## License Unlicense (Public Domain)