Skip to content

Commit

Permalink
add wallet change events
Browse files Browse the repository at this point in the history
  • Loading branch information
pablof7z committed Oct 6, 2024
1 parent 8852717 commit d3b2589
Show file tree
Hide file tree
Showing 9 changed files with 165 additions and 24 deletions.
6 changes: 6 additions & 0 deletions ndk-wallet/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
# @nostr-dev-kit/ndk-cache-redis

## 0.3.1

### Patch Changes

- publish wallet change events

## 0.3.0

### Minor Changes
Expand Down
2 changes: 1 addition & 1 deletion ndk-wallet/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nostr-dev-kit/ndk-wallet",
"version": "0.3.0",
"version": "0.3.1",
"description": "NDK Wallet",
"main": "./dist/index.js",
"module": "./dist/index.mjs",
Expand Down
10 changes: 10 additions & 0 deletions ndk-wallet/src/cashu/deposit.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { NDKCashuToken } from "./token";
import createDebug from "debug";
import { NDKEvent, NDKKind, NDKTag, NostrEvent } from "@nostr-dev-kit/ndk";
import { getBolt11ExpiresAt } from "../lib/ln";
import { NDKWalletChange } from "./history";

const d = createDebug("ndk-wallet:cashu:deposit");

Expand Down Expand Up @@ -136,6 +137,15 @@ export class NDKCashuDeposit extends EventEmitter<{

await tokenEvent.publish(this.wallet.relaySet);

const historyEvent = new NDKWalletChange(this.wallet.event.ndk);
historyEvent.direction = 'in';
historyEvent.amount = tokenEvent.amount;
historyEvent.unit = this.unit;
historyEvent.createdTokens = [ tokenEvent ];
historyEvent.description = "Deposit";
historyEvent.mint = this.mint;
historyEvent.publish(this.wallet.relaySet);

this.emit("success", tokenEvent);
} catch (e: any) {
console.log("relayset", this.wallet.relaySet);
Expand Down
77 changes: 76 additions & 1 deletion ndk-wallet/src/cashu/history.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { NDKTag, NostrEvent } from "@nostr-dev-kit/ndk";
import type NDK from "@nostr-dev-kit/ndk";
import { NDKEvent, NDKKind } from "@nostr-dev-kit/ndk";
import createDebug from "debug";
import { NDKCashuToken } from "./token";

const d = createDebug("ndk-wallet:wallet-change");

Expand All @@ -11,6 +12,8 @@ const MARKERS = {
DESTROYED: "destroyed",
};

export type DIRECTIONS = 'in' | 'out';

/**
* This class represents a balance change in the wallet, whether money being added or removed.
*/
Expand Down Expand Up @@ -45,6 +48,78 @@ export class NDKWalletChange extends NDKEvent {
return walletChange;
}

set direction(direction: DIRECTIONS | undefined) {
this.removeTag('direction')
if (direction) this.tags.push(['direction', direction])
}

get direction(): DIRECTIONS | undefined {
return this.tagValue('direction') as DIRECTIONS | undefined;
}

set amount(amount: number) {
this.removeTag('amount')
this.tags.push(['amount', amount.toString()])
}

get amount(): number | undefined {
return this.tagValue('amount') as number | undefined;
}

set fee(fee: number) {
this.removeTag('fee')
this.tags.push(['fee', fee.toString()])
}

get fee(): number | undefined {
return this.tagValue('fee') as number | undefined;
}

set unit(unit: string | undefined) {
this.removeTag('unit')
if (unit) this.tags.push(['unit', unit.toString()])
}

get unit(): string | undefined {
return this.tagValue('unit');
}

set description(description: string | undefined) {
this.removeTag('description')
if (description) this.tags.push(['description', description.toString()])
}

get description(): string | undefined {
return this.tagValue('description');
}

set mint(mint: string | undefined) {
this.removeTag('mint')
if (mint) this.tags.push(['mint', mint.toString()])
}

get mint(): string | undefined {
return this.tagValue('mint');
}

/**
* Tags tokens that were created in this history event
*/
set destroyedTokens(events: NDKCashuToken[]) {
for (const event of events) {
this.tag(event, MARKERS.DESTROYED)
}
}

/**
* Tags tokens that were created in this history event
*/
set createdTokens(events: NDKCashuToken[]) {
for (const event of events) {
this.tag(event, MARKERS.CREATED)
}
}

public addRedeemedNutzap(event: NDKEvent) {
this.tag(event, MARKERS.REDEEMED);
}
Expand All @@ -66,7 +141,7 @@ export class NDKWalletChange extends NDKEvent {

const user = await this.ndk!.signer!.user();

await this.encrypt(user);
await this.encrypt(user, undefined, "nip44");

return super.toNostrEvent(pubkey) as unknown as NostrEvent;
}
Expand Down
35 changes: 30 additions & 5 deletions ndk-wallet/src/cashu/pay/ln.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { CashuWallet, CashuMint } from "@cashu/cashu-ts";
import { CashuWallet, CashuMint, Proof } from "@cashu/cashu-ts";
import type { LnPaymentInfo } from "@nostr-dev-kit/ndk";
import type { NDKCashuPay } from "../pay";
import type { TokenSelection } from "../proofs";
import { rollOverProofs, chooseProofsForPr } from "../proofs";
import type { MintUrl } from "../mint/utils";
import { NDKCashuWallet } from "../wallet";
import { NDKWalletChange } from "../history";

export async function payLn(this: NDKCashuPay, useMint?: MintUrl): Promise<string | undefined> {
const mintBalances = this.wallet.mintBalances;
Expand Down Expand Up @@ -148,10 +149,13 @@ async function executePayment(
debug: NDKCashuPay["debug"]
): Promise<string | null> {
const _wallet = new CashuWallet(new CashuMint(selection.mint));

if (!selection.quote) throw new Error("No quote provided")

debug(
"Attempting LN payment for %d sats (%d in fees) with proofs %o, %s",
selection.quote!.amount,
selection.quote!.fee_reserve,
selection.quote.amount,
selection.quote.fee_reserve,
selection.usedProofs,
pr
);
Expand All @@ -160,16 +164,37 @@ async function executePayment(
const result = await _wallet.payLnInvoice(pr, selection.usedProofs, selection.quote);
debug("Payment result: %o", result);

const fee = calculateFee(selection.quote.amount, selection.usedProofs, result.change);

function calculateFee(sentAmount: number, proofs: Proof[], change: Proof[]) {
let fee = -sentAmount;
for (const proof of proofs) fee += proof.amount;
for (const proof of change) fee -= proof.amount;
return fee;
}

// generate history event
if (result.isPaid && result.preimage) {
debug("Payment successful");
rollOverProofs(selection, result.change, selection.mint, wallet);
const { destroyedTokens, createdToken } = await rollOverProofs(selection, result.change, selection.mint, wallet);
const historyEvent = new NDKWalletChange(wallet.ndk);
historyEvent.destroyedTokens = destroyedTokens;
if (createdToken) historyEvent.createdTokens = [createdToken];
historyEvent.tag(wallet.event);
historyEvent.direction = 'out';
historyEvent.description = 'Lightning payment';
historyEvent.tags.push(['preimage', result.preimage]);
historyEvent.amount = selection.quote.amount;
historyEvent.fee = fee;
historyEvent.publish(wallet.relaySet);

return result.preimage;
}
} catch (e) {
debug("Failed to pay with mint %s", e.message);
if (e?.message.match(/already spent/i)) {
debug("Proofs already spent, rolling over");
rollOverProofs(selection, [], selection.mint, wallet);
rollOverProofs(selection, [], selection.mint, wallet, 'out', 'Failed Lightning payment');
}
throw e;
}
Expand Down
39 changes: 24 additions & 15 deletions ndk-wallet/src/cashu/proofs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,15 +113,20 @@ function chooseProofsForQuote(
return { ...res, quote };
}

export type ROLL_OVER_RESULT = {
destroyedTokens: NDKCashuToken[],
createdToken: NDKCashuToken | undefined
};

/**
* Deletes and creates new events to reflect the new state of the proofs
*/
export async function rollOverProofs(
proofs: TokenSelection,
changes: Proof[],
mint: string,
wallet: NDKCashuWallet
) {
wallet: NDKCashuWallet,
): Promise<ROLL_OVER_RESULT> {
const relaySet = wallet.relaySet;

if (proofs.usedTokens.length > 0) {
Expand Down Expand Up @@ -152,20 +157,24 @@ export async function rollOverProofs(
proofsToSave.push(change);
}

if (proofsToSave.length === 0) {
d("no new proofs to save");
return;
}
let createdToken: NDKCashuToken | undefined;

if (proofsToSave.length > 0) {
createdToken = new NDKCashuToken(wallet.ndk);
createdToken.proofs = proofsToSave;
createdToken.mint = mint;
createdToken.wallet = wallet;
await createdToken.sign();
d("saving %d new proofs", proofsToSave.length);

const tokenEvent = new NDKCashuToken(wallet.ndk);
tokenEvent.proofs = proofsToSave;
tokenEvent.mint = mint;
tokenEvent.wallet = wallet;
await tokenEvent.sign();
d("saving %d new proofs", proofsToSave.length);
wallet.addToken(createdToken);

wallet.addToken(tokenEvent);
await createdToken.publish(wallet.relaySet);
d("created new token event", createdToken.rawEvent());
}

tokenEvent.publish(wallet.relaySet);
d("created new token event", tokenEvent.rawEvent());
return {
destroyedTokens: proofs.usedTokens,
createdToken,
}
}
17 changes: 16 additions & 1 deletion ndk-wallet/src/cashu/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,17 @@ export class NDKCashuWallet extends EventEmitter<NDKWalletEvents> implements NDK

constructor(ndk: NDK, event?: NDKEvent) {
super();
if (!ndk) throw new Error("no ndk instance");
this.ndk = ndk;
if (!event) {
event = new NDKEvent(ndk);
event.kind = NDKKind.CashuWallet;
event.dTag = Math.random().toString(36).substring(3);
event.tags = [];
}

this.event = event;
this.event.ndk = ndk;
}

set event(e: NDKEvent) {
Expand Down Expand Up @@ -223,6 +226,7 @@ export class NDKCashuWallet extends EventEmitter<NDKWalletEvents> implements NDK
* Whether this wallet has been deleted
*/
get isDeleted(): boolean {
if (!this.event?.tags) return false;
return this.event.tags.some((t) => t[0] === "deleted");
}

Expand Down Expand Up @@ -287,6 +291,17 @@ export class NDKCashuWallet extends EventEmitter<NDKWalletEvents> implements NDK
return deposit;
}

public async addHistoryItem(
direction: "in" | "out",
amount: number,
token?: NDKCashuToken
) {
const historyEvent = new NDKWalletChange(this.event.ndk);
historyEvent.tag(this.event);
await historyEvent.sign();
historyEvent.publish(this.relaySet);
}

/**
* Pay a LN invoice with this wallet
*/
Expand Down
1 change: 1 addition & 0 deletions ndk-wallet/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export * from "./wallet/index.js";
export * from "./cashu/wallet.js";
export * from "./cashu/token.js";
export * from "./cashu/deposit.js";
export * from "./cashu/history.js";
export * from "./cashu/mint/utils";

export * from "./ln/index.js";
Expand Down
2 changes: 1 addition & 1 deletion ndk-wallet/src/wallet/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export interface NDKWallet
/**
* Emitted when a balance is known to have been updated.
*/
balance_update: (balance: NDKWalletBalance) => void;
balance_updated: (balance?: NDKWalletBalance) => void;
}> {
get status(): NDKWalletStatus;
get type(): string;
Expand Down

0 comments on commit d3b2589

Please sign in to comment.