@cashu/cashu-ts
Version:
cashu library for communicating with a cashu mint
126 lines (96 loc) • 4.87 kB
Markdown
```ts
import { Wallet } from '@cashu/cashu-ts';
const mintUrl = 'http://localhost:3338';
const wallet = new Wallet(mintUrl);
await wallet.loadMint(); // wallet is now ready to use
const invoice = 'lnbc......'; // Lightning invoice to pay
const meltQuote = await wallet.createMeltQuoteBolt11(invoice);
const amountToSend = meltQuote.amount.add(meltQuote.fee_reserve);
// Wallet.send performs coin selection and swaps the proofs with the mint
// if no appropriate amount can be selected offline. When selecting coins for a
// melt, we must include the mint and/or lightning fees to ensure there are
// sufficient funds to cover the invoice.
const { keep: proofsToKeep, send: proofsToSend } = await wallet.send(amountToSend, proofs, {
includeFees: true,
});
const meltResponse = await wallet.meltProofsBolt11(meltQuote, proofsToSend);
// store proofsToKeep and meltResponse.change in your app ..
```
The two-step flow lets you persist the preview before paying. This is the recommended pattern when
you want replay-safe recovery alongside NUT-19 transport retries.
```ts
import { Wallet } from '@cashu/cashu-ts';
const wallet = new Wallet('http://localhost:3338');
await wallet.loadMint();
const meltQuote = await wallet.createMeltQuoteBolt11(invoice);
const amountToSend = meltQuote.amount.add(meltQuote.fee_reserve);
const { send: proofsToSend } = await wallet.send(amountToSend, proofs, {
includeFees: true,
});
const meltPreview = await wallet.prepareMelt('bolt11', meltQuote, proofsToSend);
// Persist an app-defined snapshot here.
// Do not call JSON.stringify(meltPreview) directly; preview objects contain non-JSON-safe values.
const meltResponse = await wallet.completeMelt(meltPreview);
```
For mints that support NUT-06 asynchronous melts, the melt response can return before the
Lightning payment completes, meaning the response will not yet carry NUT-08 change signatures.
To reclaim that change later, persist the prepared output data while the melt is pending, then
hydrate change proofs from the eventual paid quote response.
```ts
import { OutputData, type SerializedOutputData } from '@cashu/cashu-ts';
// 1. Prepare the melt and persist the change-output data alongside the pending quote.
const preview = await wallet.prepareMelt('bolt11', meltQuote, myProofs);
const stored = JSON.stringify(preview.outputData.map((o) => OutputData.serialize(o)));
await wallet.completeMelt(preview, undefined, { preferAsync: true });
// 2. ... time passes ... use checkMeltQuote*() or wallet.on.onceMeltPaid() to learn the
// quote is paid. The paid response carries the change signatures.
// 3. Restore the output data and reconstruct spendable change proofs.
const restored = (JSON.parse(stored) as SerializedOutputData[]).map((s) =>
OutputData.deserialize(s),
);
const change = wallet.createMeltChangeProofs(restored, paidQuote.change ?? []);
```
- `OutputData.serialize` / `OutputData.deserialize` are the JSON-safe round-trip primitives
(decimal-encoded bigints, hex-encoded bytes; preserves `ephemeralE` for P2BK).
- `wallet.createMeltChangeProofs` runs the same validation as the synchronous path and is also
callable directly if you've persisted output data outside the helpers above (crash recovery,
process hand-off, etc.).
- For the same pattern using the WalletOps builder (`.prepare()`), see
[Wallet Operations – Melt § 4](../wallet_ops/melt.md).
## 4) Generic melt for custom payment methods
The generic `createMeltQuote()` / `meltProofs()` methods support arbitrary payment methods without
requiring first-class library support.
The mint must advertise the method at `/v1/melt/quote/{method}`.
```ts
import { Wallet, Amount, type MeltQuoteBaseResponse, type AmountLike } from '@cashu/cashu-ts';
// Define your custom quote response type
type BacsMeltQuoteResponse = MeltQuoteBaseResponse & {
fee_estimate: Amount;
reference: string;
};
const wallet = new Wallet('http://localhost:3338');
await wallet.loadMint();
// Create a melt quote using the generic method
const meltQuote = await wallet.createMeltQuote<BacsMeltQuoteResponse>(
'bacs',
{
request: 'GB29NWBK60161331926819', // IBAN
amount: 5000n,
},
{
normalize: (raw) => ({
...(raw as BacsMeltQuoteResponse),
fee_estimate: Amount.from(raw.fee_estimate as AmountLike),
}),
},
);
// Coin select and melt
const totalNeeded = meltQuote.amount.add(meltQuote.fee_estimate);
const { send: proofsToSend } = await wallet.send(totalNeeded, proofs, { includeFees: true });
const meltResponse = await wallet.meltProofs('bacs', meltQuote, proofsToSend);
```