API Wallets
Browser extensions and hardware wallets via CIP-30
API Wallets
API wallets connect to external signing devices through the CIP-30 standard. Keys never leave the user's device—your application only requests signatures, which the wallet prompts the user to approve.
What They Are
Interfaces to browser extensions (Eternl, Lace, Flint) and hardware wallets (Ledger, Trezor) following the CIP-30 standard. Your application receives a wallet API object and uses it to request operations.
When to Use
- dApps: Decentralized applications where users control funds
- NFT marketplaces: User-to-user transactions
- DeFi interfaces: Swaps, staking, lending protocols
- Any end-user application: Where users maintain custody
Why They Work
User custody model—keys stay on user's device or hardware wallet. Your application proposes operations, user approves through their trusted wallet interface. No key management burden on your application.
How to Secure
Never request or store user keys. Frontend signs only and should not include provider configuration. Backend builds transactions using read-only wallets with the user's address. This separation of concerns provides security by design. Never try to access wallet internals. Never cache signatures.
Supported Wallets
- Eternl - Popular browser extension
- Lace - Light wallet by IOG
- Flint - Feature-rich extension
- Typhon - Advanced features
- Hardware wallets - Ledger, Trezor (through extensions)
Frontend Pattern (Signing Only)
Frontend creates API wallet client without provider. Can sign and submit transactions but cannot build them.
import { } from "@evolution-sdk/evolution";
declare const : any; // window.cardano
async function () {
// 1. User connects browser extension (CIP-30)
const = await .eternl.enable();
// 2. Create API wallet client (no provider needed for signing/submitting)
const = ({
: "mainnet",
: { : "api", : }
});
// 3. Get user address
const = await .();
.("User address:", );
// 4. Request backend to build transaction (backend has provider + read-only wallet)
const = "84a400..."; // From backend
// 5. Sign with user's wallet (prompts user approval)
const = await .();
// 6. Submit directly through wallet API (CIP-30 wallets can submit)
const = await .();
.("Transaction submitted:", );
}Configuration Options
interface WalletApi {
// CIP-30 wallet API interface
(): <number>;
(): <string[] | undefined>;
// ... other CIP-30 methods
}
interface ApiWalletConfig {
: "api"
: WalletApi // CIP-30 wallet API object (required)
}Complete dApp Example
import { } from "@evolution-sdk/evolution";
declare const : any;
async function () {
// Detect available wallets (CIP-30 extensions)
const = .().(
=> []?.enable
);
.("Available wallets:", );
// User selects wallet
const = "eternl"; // or "lace", "flint", etc.
// Request connection (CIP-30)
const = await [].enable();
// Create API wallet client
const = ({
: "mainnet",
: { : "api", : }
});
// Get user address
const = await .();
// Send address to backend for transaction building
const = await ("/api/build-payment", {
: "POST",
: { "Content-Type": "application/json" },
: .({
: ,
: "addr1...",
: "5000000"
})
});
const { } = await .();
// Sign with user wallet (prompts user for approval)
const = await .();
// Submit directly via wallet API (CIP-30 wallets can submit)
const = await .();
.("Payment sent:", );
}Hardware Wallet Support
Hardware wallets work through browser extensions. The extension communicates with the hardware device.
import { } from "@evolution-sdk/evolution";
declare const : any;
async function () {
// User connects Ledger/Trezor through browser extension
const = await .eternl.enable(); // Extension handles hardware
// Create API wallet client (same as software wallets)
const = ({
: "mainnet",
: { : "api", : }
});
// Extension will prompt hardware wallet for signatures
const = "84a400..."; // Transaction CBOR from backend
const = await .();
}Derivation Paths
Hardware wallets and extensions follow Cardano's BIP-32 derivation paths:
| Path | Account | Address | Use Case |
|---|---|---|---|
m/1852'/1815'/0'/0/0 | 0 | 0 | Standard first address |
m/1852'/1815'/0'/0/1 | 0 | 1 | Second address, same account |
m/1852'/1815'/1'/0/0 | 1 | 0 | Second account first address |
m/1852'/1815'/0'/2/0 | 0 | 0 (stake) | Staking key derivation |
Path breakdown:
1852': Purpose (Cardano)1815': Coin type (ADA)0': Account index0: Change chain (0=external, 1=internal, 2=staking)0: Address index
Error Handling
import { } from "@evolution-sdk/evolution";
declare const : any;
async function (: string) {
try {
// Check if wallet exists
if (![]) {
throw new (`${} not installed`);
}
// Request connection using CIP-30
const = await [].enable();
// Create API wallet client
const = ({
: "mainnet",
: { : "api", : }
});
return ;
} catch (: any) {
if (.code === 2) {
.("User rejected connection");
} else if (.code === 3) {
.("Account not found");
} else {
.("Connection failed:", );
}
throw ;
}
}Best Practices
Always show wallet selection UI so users can choose their preferred wallet. Handle user rejection gracefully without breaking your application flow. Display transaction details before requesting signature to maintain transparency. Show loading states during signing operations. Provide clear success and error feedback to keep users informed. Cache wallet choice in localStorage to improve user experience. Allow wallet disconnection so users can switch wallets or disconnect. Never auto-connect without explicit user action. Never hide transaction details from users. Never batch operations without explicit user consent for each action.
Next Steps
- Client patterns - Frontend/backend architecture and client patterns
- Security - Complete security guide
- Seed Phrase - Development wallets