Evolution SDK
Wallets

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:

PathAccountAddressUse Case
m/1852'/1815'/0'/0/000Standard first address
m/1852'/1815'/0'/0/101Second address, same account
m/1852'/1815'/1'/0/010Second account first address
m/1852'/1815'/0'/2/000 (stake)Staking key derivation

Path breakdown:

  • 1852': Purpose (Cardano)
  • 1815': Coin type (ADA)
  • 0': Account index
  • 0: 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