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

    DocumentsWallet OperationsSend

    Wallet Operations: Send

    const { keep, send } = await wallet.ops.send(5, myProofs).run();

    // Or use prepare() instead of run() to do a dry run preview first
    const preview = await wallet.ops.send(5, myProofs).prepare();
    const { keep, send } = await wallet.completeSwap(preview);
    • Uses wallet policy for both send and keep.
    • If you only customize send, keep is omitted so the wallet may still attempt an offline exact match where possible. This avoids mint fees.
    const { keep, send } = await wallet.ops
    .send(15, myProofs)
    .asDeterministic(0, [4, 4]) // counter=0 => auto-reserve; split must include 2x 4's
    .keepAsRandom() // change proofs must have random secrets
    .run();

    Note Passing counter=0 means "reserve counters automatically" using wallet CounterSource.

    const { keep, send } = await wallet.ops
    .send(10, myProofs)
    .asP2PK({ pubkey, locktime: 1712345678 })
    .includeFees(true) // sender covers receiver’s future spend fee
    .run();
    const { keep, send } = await wallet.ops
    .send(20, myProofs)
    .asFactory(makeOutputData, [4, 8, 8]) // makeOutputData: OutputDataFactory
    .keepAsDeterministic() // deterministic change, auto-reserve
    .keyset('0123456')
    .onCountersReserved((info) => {
    console.log('Reserved counters', info);
    })
    .run();
    const mySendData: OutputData[] = [
    /* amounts must sum to 15 */
    ];

    const { keep, send } = await wallet.ops.send(15, myProofs).asCustom(mySendData).run();

    Custom data may name any active keyset of the wallet unit, per output. The wallet checks each keyset before the swap and unblinds every output with the keyset the mint signed under.

    Exact match only (throws on no exact match):

    const { keep, send } = await wallet.ops
    .send(7, myProofs)
    .offlineExactOnly(/* requireDleq? */ false)
    .includeFees(true) // optional; applied to the offline selection rules
    .run();

    Close match allowed (overspend permitted by wallet RGLI):

    const { keep, send } = await wallet.ops
    .send(7, myProofs)
    .offlineCloseMatch(/* requireDleq? */ true) // only proofs with valid DLEQ
    .run();

    Important Offline modes cannot be combined with custom output types (asXXXX/keepAsXXXX). The builder will throw: Offline selection cannot be combined with custom output types. Remove send/keep output configuration, or use an online swap.

    A one-shot run() that dies between the mint's reply and your storage write has spent the inputs without you ever seeing the new proofs. Persisting the preview closes that window: completeSwap builds its request purely from the preview, so replaying a persisted preview posts a byte-identical /v1/swap body, and a mint that caches the endpoint (NUT-19) returns the original signatures.

    import { deserializeSwapPreview, serializeSwapPreview } from '@cashu/cashu-ts';

    const preview = await wallet.ops.send(21, myProofs).prepare();

    // Unselected proofs are not part of the replay and are not serialized: return them to
    // storage yourself, and add them back to `keep` after a replayed completeSwap.
    const backToStorage = preview.unselectedProofs ?? [];

    // Persist before completing. Previews contain Amount, bigint and Uint8Array values,
    // so use the helper rather than calling JSON.stringify(preview) directly.
    const stored = JSON.stringify(serializeSwapPreview(preview));

    const { keep, send } = await wallet.completeSwap(preview);

    // ... after a restart: load the mint again, then replay the same preview ...
    const { keep: change, send: recovered } = await wallet.completeSwap(
    deserializeSwapPreview(JSON.parse(stored)),
    );

    The serialized preview contains inputs in the clear, so it is spendable bearer material. Store it with the same protection as the proof database, and delete it once the swap settles.

    The replay window has bounds:

    • The mint must advertise /v1/swap in its NUT-19 cached_endpoints, and the replay must happen inside the advertised TTL. See NUT-19 Cached Responses.
    • Automatic NUT-19 retries only cover failures inside a running process. The persisted preview is what covers a process restart.
    • A preview and a seed protect different windows: the preview covers a restart inside the TTL; deterministic secrets plus NUT-09 restore cover loss after it.