Skip to content

Commit

Permalink
Merge Dev: v1.2.0-1
Browse files Browse the repository at this point in the history
  • Loading branch information
Egge21M committed Oct 19, 2024
1 parent f900f64 commit 55871bc
Show file tree
Hide file tree
Showing 17 changed files with 479 additions and 86 deletions.
9 changes: 4 additions & 5 deletions .eslintrc.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
// .eslintrc
{
"parser": "@typescript-eslint/parser",
"parserOptions": {
Expand All @@ -10,12 +9,12 @@
"extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"],

"rules": {
"@typescript-eslint/no-unused-vars": "warn",
// to enforce using type for object type definitions, can be type or interface
"@typescript-eslint/no-unused-vars": "error",
"@typescript-eslint/typedef": ["error", { "parameter": true, "arrowParameter": true }],
"@typescript-eslint/consistent-type-definitions": ["warn", "type"],
"@typescript-eslint/array-type": ["warn", { "default": "generic" }],
"@typescript-eslint/array-type": ["error", { "default": "generic" }],
"require-await": "off",
"@typescript-eslint/require-await": "warn",
"@typescript-eslint/require-await": "error",
"@typescript-eslint/await-thenable": "warn",
"@typescript-eslint/consistent-type-exports": "warn",
"no-else-return": "warn"
Expand Down
24 changes: 24 additions & 0 deletions .github/workflows/nextVersion.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
name: Publish Package to npmjs
permissions:
contents: write
id-token: write
on:
push:
branches:
- staging
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: 20
registry-url: 'https://registry.npmjs.org'
- run: npm i
- run: npm run compile
- run: npm version prerelease --preid=rc --no-git-tag-version
- run: git push
- run: npm publish --provenance --access public --tag next
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
21 changes: 21 additions & 0 deletions .github/workflows/prerelease.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Publish Package to npmjs
permissions:
contents: read
id-token: write
on:
release:
types: [prereleased]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v3
with:
node-version: 20
registry-url: 'https://registry.npmjs.org'
- run: npm ci
- run: npm run compile
- run: npm publish --tag next --provenance
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}
3 changes: 1 addition & 2 deletions src/CashuMint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ import type {
MintResponse,
PostRestorePayload,
MeltQuotePayload,
MeltQuoteResponse,
MintContactInfo
MeltQuoteResponse
} from './model/types/index.js';
import { MeltQuoteState } from './model/types/index.js';
import request from './request.js';
Expand Down
103 changes: 69 additions & 34 deletions src/CashuWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,17 @@ import {
type TokenEntry,
CheckStateEnum,
SerializedBlindedSignature,
MeltQuoteState
MeltQuoteState,
CheckStateEntry,
Preferences
} from './model/types/index.js';
import {
bytesToNumber,
getDecodedToken,
getDefaultAmountPreference,
splitAmount
} from './utils.js';
import { isAmountPreferenceArray, deprecatedAmountPreferences } from './legacy/cashu-ts';
import { validateMnemonic } from '@scure/bip39';
import { wordlist } from '@scure/bip39/wordlists/english';
import { hashToCurve, pointFromHex } from '@cashu/crypto/modules/common';
Expand Down Expand Up @@ -164,17 +167,18 @@ class CashuWallet {
}
): Promise<Array<Proof>> {
const proofs: Array<Proof> = [];
const amount = tokenEntry.proofs.reduce((total, curr) => total + curr.amount, 0);
const amount = tokenEntry.proofs.reduce((total: number, curr: Proof) => total + curr.amount, 0);
let preference = options?.preference;
const keys = await this.getKeys(options?.keysetId);
if (!preference) {
preference = getDefaultAmountPreference(amount);
preference = getDefaultAmountPreference(amount, keys);
}
const keys = await this.getKeys(options?.keysetId);
const pref: Preferences = { sendPreference: preference };
const { payload, blindedMessages } = this.createSwapPayload(
amount,
tokenEntry.proofs,
keys,
preference,
pref,
options?.counter,
options?.pubkey,
options?.privkey
Expand Down Expand Up @@ -206,21 +210,27 @@ class CashuWallet {
amount: number,
proofs: Array<Proof>,
options?: {
preference?: Array<AmountPreference>;
preference?: Preferences | Array<AmountPreference>;
counter?: number;
pubkey?: string;
privkey?: string;
keysetId?: string;
}
): Promise<SendResponse> {
if (options?.preference) {
amount = options?.preference?.reduce((acc, curr) => acc + curr.amount * curr.count, 0);
if (isAmountPreferenceArray(options.preference)) {
options.preference = deprecatedAmountPreferences(options.preference);
}
amount = options?.preference?.sendPreference.reduce(
(acc: number, curr: AmountPreference) => acc + curr.amount * curr.count,
0
);
}
const keyset = await this.getKeys(options?.keysetId);
let amountAvailable = 0;
const proofsToSend: Array<Proof> = [];
const proofsToKeep: Array<Proof> = [];
proofs.forEach((proof) => {
proofs.forEach((proof: Proof) => {
if (amountAvailable >= amount) {
proofsToKeep.push(proof);
return;
Expand Down Expand Up @@ -254,7 +264,7 @@ class CashuWallet {
const splitProofsToKeep: Array<Proof> = [];
const splitProofsToSend: Array<Proof> = [];
let amountKeepCounter = 0;
proofs.forEach((proof) => {
proofs.forEach((proof: Proof) => {
if (amountKeepCounter < amountKeep) {
amountKeepCounter += proof.amount;
splitProofsToKeep.push(proof);
Expand Down Expand Up @@ -294,9 +304,11 @@ class CashuWallet {
const { outputs, promises } = await this.mint.restore({ outputs: blindedMessages });

// Collect and map the secrets and blinding factors with the blinded messages that were returned from the mint
const validRs = rs.filter((r, i) => outputs.map((o) => o.B_).includes(blindedMessages[i].B_));
const validSecrets = secrets.filter((s, i) =>
outputs.map((o) => o.B_).includes(blindedMessages[i].B_)
const validRs = rs.filter((_: bigint, i: number) =>
outputs.map((o: SerializedBlindedMessage) => o.B_).includes(blindedMessages[i].B_)
);
const validSecrets = secrets.filter((_: Uint8Array, i: number) =>
outputs.map((o: SerializedBlindedMessage) => o.B_).includes(blindedMessages[i].B_)
);

return {
Expand All @@ -312,9 +324,9 @@ class CashuWallet {
const allKeys = await this.mint.getKeys(keysetId);
let keys;
if (keysetId) {
keys = allKeys.keysets.find((k) => k.id === keysetId);
keys = allKeys.keysets.find((k: MintKeys) => k.id === keysetId);
} else {
keys = allKeys.keysets.find((k) => (unit ? k.unit === unit : k.unit === 'sat'));
keys = allKeys.keysets.find((k: MintKeys) => (unit ? k.unit === unit : k.unit === 'sat'));
}
if (!keys) {
throw new Error(
Expand All @@ -331,12 +343,14 @@ class CashuWallet {
/**
* Requests a mint quote form the mint. Response returns a Lightning payment request for the requested given amount and unit.
* @param amount Amount requesting for mint.
* @param description optional description for the mint quote
* @returns the mint will return a mint quote with a Lightning invoice for minting tokens of the specified amount and unit
*/
async createMintQuote(amount: number) {
async createMintQuote(amount: number, description?: string) {
const mintQuotePayload: MintQuotePayload = {
unit: this._unit,
amount: amount
amount: amount,
description: description
};
return await this.mint.createMintQuote(mintQuotePayload);
}
Expand Down Expand Up @@ -369,7 +383,7 @@ class CashuWallet {
const keyset = await this.getKeys(options?.keysetId);
const { blindedMessages, secrets, rs } = this.createRandomBlindedMessages(
amount,
options?.keysetId ?? keyset.id,
keyset,
options?.preference,
options?.counter,
options?.pubkey
Expand Down Expand Up @@ -415,6 +429,7 @@ class CashuWallet {
* @param proofsToSend proofs to melt
* @param options.keysetId? optionally set keysetId for blank outputs for returned change.
* @param options.counter? optionally set counter to derive secret deterministically. CashuWallet class must be initialized with seed phrase to take effect
* @param options.privkey? optionally set a private key to unlock P2PK locked secrets
* @returns
*/
async meltTokens(
Expand All @@ -423,6 +438,7 @@ class CashuWallet {
options?: {
keysetId?: string;
counter?: number;
privkey?: string;
}
): Promise<MeltTokensResponse> {
const keys = await this.getKeys(options?.keysetId);
Expand All @@ -432,6 +448,19 @@ class CashuWallet {
keys.id,
options?.counter
);
if (options?.privkey != undefined) {
proofsToSend = getSignedProofs(
proofsToSend.map((p: Proof) => {
return {
amount: p.amount,
C: pointFromHex(p.C),
id: p.id,
secret: new TextEncoder().encode(p.secret)
};
}),
options.privkey
).map((p: NUT11Proof) => serializeProof(p));
}
const meltPayload: MeltPayload = {
quote: meltQuote.quote,
inputs: proofsToSend,
Expand All @@ -456,6 +485,7 @@ class CashuWallet {
* @param meltQuote melt quote for the invoice
* @param options.keysetId? optionally set keysetId for blank outputs for returned change.
* @param options.counter? optionally set counter to derive secret deterministically. CashuWallet class must be initialized with seed phrase to take effect
* @param options.privkey? optionally set a private key to unlock P2PK locked secrets
* @returns
*/
async payLnInvoice(
Expand All @@ -465,14 +495,16 @@ class CashuWallet {
options?: {
keysetId?: string;
counter?: number;
privkey?: string;
}
): Promise<MeltTokensResponse> {
if (!meltQuote) {
meltQuote = await this.mint.createMeltQuote({ unit: this._unit, request: invoice });
}
return await this.meltTokens(meltQuote, proofsToSend, {
keysetId: options?.keysetId,
counter: options?.counter
counter: options?.counter,
privkey: options?.privkey
});
}

Expand All @@ -495,8 +527,8 @@ class CashuWallet {
): Promise<MeltTokensResponse> {
const decodedToken = getDecodedToken(token);
const proofs = decodedToken.token
.filter((x) => x.mint === this.mint.mintUrl)
.flatMap((t) => t.proofs);
.filter((x: TokenEntry) => x.mint === this.mint.mintUrl)
.flatMap((t: TokenEntry) => t.proofs);
return this.payLnInvoice(invoice, proofs, meltQuote, {
keysetId: options?.keysetId,
counter: options?.counter
Expand All @@ -517,34 +549,37 @@ class CashuWallet {
amount: number,
proofsToSend: Array<Proof>,
keyset: MintKeys,
preference?: Array<AmountPreference>,
preference?: Preferences | Array<AmountPreference>,
counter?: number,
pubkey?: string,
privkey?: string
): {
payload: SwapPayload;
blindedMessages: BlindedTransaction;
} {
const totalAmount = proofsToSend.reduce((total, curr) => total + curr.amount, 0);
if (isAmountPreferenceArray(preference)) {
preference = deprecatedAmountPreferences(preference);
}
const totalAmount = proofsToSend.reduce((total: number, curr: Proof) => total + curr.amount, 0);
const keepBlindedMessages = this.createRandomBlindedMessages(
totalAmount - amount,
keyset.id,
undefined,
keyset,
preference?.keepPreference,
counter
);
if (this._seed && counter) {
counter = counter + keepBlindedMessages.secrets.length;
}
const sendBlindedMessages = this.createRandomBlindedMessages(
amount,
keyset.id,
preference,
keyset,
preference?.sendPreference,
counter,
pubkey
);
if (privkey) {
proofsToSend = getSignedProofs(
proofsToSend.map((p) => {
proofsToSend.map((p: Proof) => {
return {
amount: p.amount,
C: pointFromHex(p.C),
Expand Down Expand Up @@ -580,15 +615,15 @@ class CashuWallet {
*/
async checkProofsSpent<T extends { secret: string }>(proofs: Array<T>): Promise<Array<T>> {
const enc = new TextEncoder();
const Ys = proofs.map((p) => hashToCurve(enc.encode(p.secret)).toHex(true));
const Ys = proofs.map((p: T) => hashToCurve(enc.encode(p.secret)).toHex(true));
const payload = {
// array of Ys of proofs to check
Ys: Ys
};
const { states } = await this.mint.check(payload);

return proofs.filter((_, i) => {
const state = states.find((state) => state.Y === Ys[i]);
return proofs.filter((_: T, i: number) => {
const state = states.find((state: CheckStateEntry) => state.Y === Ys[i]);
return state && state.state === CheckStateEnum.SPENT;
});
}
Expand All @@ -612,13 +647,13 @@ class CashuWallet {
*/
private createRandomBlindedMessages(
amount: number,
keysetId: string,
keyset: MintKeys,
amountPreference?: Array<AmountPreference>,
counter?: number,
pubkey?: string
): BlindedMessageData & { amounts: Array<number> } {
const amounts = splitAmount(amount, amountPreference);
return this.createBlindedMessages(amounts, keysetId, counter, pubkey);
const amounts = splitAmount(amount, keyset.keys, amountPreference);
return this.createBlindedMessages(amounts, keyset.id, counter, pubkey);
}

/**
Expand Down Expand Up @@ -713,7 +748,7 @@ class CashuWallet {
const A = pointFromHex(keyset.keys[p.amount]);
return constructProofFromPromise(blindSignature, r, secret, A);
})
.map((p) => serializeProof(p) as Proof);
.map((p: NUT11Proof) => serializeProof(p) as Proof);
}
}

Expand Down
Loading

0 comments on commit 55871bc

Please sign in to comment.