Cashu-TS - v4.11.0
    Preparing search index...

    Documents › Usage Examples › Create Wallet

    Create a wallet

    There are a number of ways to instantiate a wallet, depending on your needs.

    Wallet classes are mostly stateless, so you can instantiate and throw them away as needed. Your app must therefore manage state, such as fetching and storing proofs in a database.

    NB: You must always call loadMint() or loadMintFromCache after instantiating a wallet.

    import { Wallet } from '@cashu/cashu-ts';

    // Simplest: With a mint URL
    const mintUrl = 'http://localhost:3338';
    const wallet1 = new Wallet(mintUrl); // unit is 'sat'
    await wallet1.loadMint(); // wallet is now ready to use

    // Persist these in your app
    const keychainCache = wallet1.keyChain.cache; // KeyChainCache
    const mintInfoCache = wallet1.getMintInfo().cache; // GetInfoResponse

    // Advanced: With cached mint data (avoids network calls on startup)
    const wallet2 = new Wallet(keychainCache.mintUrl); // unit defaults to 'sat'
    wallet2.loadMintFromCache(mintInfoCache, keychainCache);
    // wallet2 is now ready to use

    ⚠️ Server-side usage: If you construct a Wallet (or Mint) using a URL from untrusted input (e.g. a received token), validate the mint URL against your own trusted-mint allowlist before passing it in. The library validates URL structure but cannot know which mints your application trusts.

    Everything recoverable in cashu-ts (deterministic secrets, NUT-09 restore, derived P2PK keys, quote locks) hangs off bip39seed: the 64-byte BIP-39 seed, not the entropy behind the mnemonic. This line accepts 16 to 64 bytes; use the 64-byte seed, which is what NUT-13 derivation expects and what v5 requires. mnemonicToSeedSync derives it from the phrase (and optional passphrase) with the library's existing primitives; it normalizes and checks the word count but does NOT validate the checksum or wordlist, so validate a user-entered phrase first, with @scure/bip39 or similar, which is also where mnemonic generation lives.

    import { Wallet, mnemonicToSeedSync } from '@cashu/cashu-ts';

    const wallet = new Wallet(mintUrl, { unit: 'sat', bip39seed: mnemonicToSeedSync(mnemonic) });
    await wallet.loadMint();

    A wallet constructed without a seed uses random secrets. Its proofs live only in your proof store and cannot be recovered from the mint.

    cashu-ts does not enforce a transport policy. It accepts http: mint and identity-provider URLs, follows redirects on bodiless GETs, and does not know which networks are private, because Tor, LAN and local-development mints are legitimate. A server-side consumer that needs such a policy applies it itself, at the seams 4.x offers:

    • Require https: and an allowlisted host when validating the mint URL, before constructing the Wallet (see the warning above). A NUT-21 openid_discovery URL deserves the same check.
    • Set redirect: 'error' process-wide with setGlobalRequestOptions; the library already forces it on requests that carry a body or an auth header.
    • For egress control, replace the request pipeline with customRequest, or apply the rule at the socket layer (for example undici's connect hook), where the resolved address is known and a hostname check is not fooled by DNS.
    • NUT-17 subscriptions have the same seam for the socket: injectWebSocketImpl(ws) replaces the WebSocket class the library captured when it loaded, for a proxy, a polyfill or a runtime without a global WebSocket. Call it before the first subscription.
    import { setGlobalRequestOptions } from '@cashu/cashu-ts';

    setGlobalRequestOptions({ redirect: 'error' });

    Retries after a 5xx on NUT-19 cached endpoints are deliberate, see NUT-19 Cached Responses: they replay an idempotent request, never a fresh one.

    Transport defaults. Every request attempt times out after 5 minutes, the bound Node's own fetch applies, unless requestTimeout says otherwise (per call or through setGlobalRequestOptions; Infinity disables it), so a mint that stops answering surfaces as a NetworkError rather than a hang. And outside browsers the library sends User-Agent: Mozilla/5.0, replacing the runtime's own string (undici, NSURLSession, OkHttp) that would otherwise fingerprint the process; requestHeaders overrides it.

    createAuthWallet connects one AuthManager and OIDCAuth to a mint and wallet. The manager owns the CAT record and BAT pool; your app owns account selection, login flow lifetime, and persistence. Keep separate managers and persisted BAT pools for separate application accounts.

    Use the attached OIDC client for sign-in or call oidc.refresh(savedRefreshToken) to restore a session. Its listener installs the complete token state. auth.setCAT(cat) is manual replacement: it clears the previous refresh token and expiry. A provider refresh keeps the token used for that request if the response omits a replacement; a new sign-in does not inherit it. Attaching a different OIDC instance clears the CAT and refresh state; initial attachment and reattaching the same instance preserve them.

    When ending an application session, cancel any device flow with its cancel() helper and clear the CAT with auth.setCAT(undefined). Existing BATs remain available; use auth.importPool([], 'replace') if they should also be discarded. Clearing the CAT does not cancel an independently started sign-in.

    oidc.onTokens(tokens, origin) observes provider responses (origin is 'signin' or 'refresh'), including refresh results the manager may decline after an account change. Persistence callbacks must follow your app's account lifetime; do not use them to call setCAT alongside the attached manager.

    Pass outputDataCreator when you need to replace the default output generation logic, for example to use a platform specific implementation for deterministic secrets.

    OutputDataCreator is the injectable strategy interface used by Wallet. The canonical default creation surface remains OutputData.create*(), so custom creators can delegate back to it for the standard random and P2PK behavior.

    Caution

    The only officially supported and maintained OutputDataCreator is the default Noble Curves based implementation exposed by OutputData.create*(). Custom creators are an escape hatch for runtime-specific needs, but their compatibility and maintenance are the integrator's responsibility.

    import { OutputData, type OutputDataCreator, Wallet } from '@cashu/cashu-ts';

    class CustomOutputDataCreator implements OutputDataCreator {
    createP2PKData(...args: Parameters<OutputDataCreator['createP2PKData']>) {
    const [p2pk, amount, keyset, customSplit] = args;
    return OutputData.createP2PKData(p2pk, amount, keyset, customSplit);
    }

    createSingleP2PKData(...args: Parameters<OutputDataCreator['createSingleP2PKData']>) {
    const [p2pk, amount, keysetId, eBytes] = args;
    // Forward eBytes as-is: a SIG_ALL batch relies on every output sharing this key.
    return OutputData.createSingleP2PKData(p2pk, amount, keysetId, eBytes);
    }

    createRandomData(...args: Parameters<OutputDataCreator['createRandomData']>) {
    const [amount, keyset, customSplit] = args;
    return OutputData.createRandomData(amount, keyset, customSplit);
    }

    createSingleRandomData(...args: Parameters<OutputDataCreator['createSingleRandomData']>) {
    const [amount, keysetId] = args;
    return OutputData.createSingleRandomData(amount, keysetId);
    }

    createDeterministicData(...args: Parameters<OutputDataCreator['createDeterministicData']>) {
    const [amount, seed, counter, keyset, customSplit] = args;
    // Replace this with your runtime-specific implementation.
    return OutputData.createDeterministicData(amount, seed, counter, keyset, customSplit);
    }

    createSingleDeterministicData(
    ...args: Parameters<OutputDataCreator['createSingleDeterministicData']>
    ) {
    const [amount, seed, counter, keysetId] = args;
    // Replace this with your runtime-specific implementation.
    return OutputData.createSingleDeterministicData(amount, seed, counter, keysetId);
    }
    }

    const wallet = new Wallet('http://localhost:3338', {
    outputDataCreator: new CustomOutputDataCreator(),
    });
    await wallet.loadMint();