Skip to content

Commit 40972cc

Browse files
committed
create ExtendedMintInfo with internal_melts_only flag
1 parent e676d5b commit 40972cc

File tree

7 files changed

+206
-24
lines changed

7 files changed

+206
-24
lines changed

app/features/accounts/account-repository.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
import { type QueryClient, useQueryClient } from '@tanstack/react-query';
88
import type { DistributedOmit } from 'type-fest';
99
import {
10-
type MintInfo,
10+
type ExtendedMintInfo,
1111
getCashuProtocolUnit,
1212
getCashuUnit,
1313
getCashuWallet,
@@ -199,7 +199,7 @@ export class AccountRepository {
199199
private async getPreloadedWallet(mintUrl: string, currency: Currency) {
200200
const seed = await this.getCashuWalletSeed?.();
201201

202-
let mintInfo: MintInfo;
202+
let mintInfo: ExtendedMintInfo;
203203
let allMintKeysets: MintAllKeysets;
204204
let mintActiveKeys: MintActiveKeys;
205205

app/features/shared/cashu.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -163,7 +163,7 @@ export const mintKeysQueryKey = (mintUrl: string, keysetId?: string) => [
163163
export const mintInfoQueryOptions = (mintUrl: string) =>
164164
queryOptions({
165165
queryKey: mintInfoQueryKey(mintUrl),
166-
queryFn: async () => getCashuWallet(mintUrl).getMintInfo(),
166+
queryFn: async () => getCashuWallet(mintUrl).getExtendedMintInfo(),
167167
staleTime: 1000 * 60 * 60, // 1 hour
168168
});
169169

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,178 @@
1+
/**
2+
* This class was copied from cashu-ts v2.7.2 and extended with the following methods:
3+
* - get iconUrl
4+
* - get internalMeltsOnly
5+
*
6+
* As of cashu-ts v2.7.2, the MintInfo class is not exported, so we need to copy it here in order to extend it.
7+
*/
8+
9+
import type {
10+
GetInfoResponse,
11+
MPPMethod,
12+
SwapMethod,
13+
WebSocketSupport,
14+
} from '@cashu/cashu-ts';
15+
16+
/**
17+
* A class that represents the data fetched from the mint's
18+
* [NUT-06 info endpoint](https://github.com/cashubtc/nuts/blob/main/06.md)
19+
*/
20+
export class ExtendedMintInfo {
21+
private readonly _mintInfo: GetInfoResponse;
22+
private readonly _protectedEnpoints?: {
23+
cache: {
24+
[url: string]: boolean;
25+
};
26+
apiReturn: Array<{
27+
method: 'GET' | 'POST';
28+
regex: RegExp;
29+
cachedValue?: boolean;
30+
}>;
31+
};
32+
33+
constructor(info: GetInfoResponse) {
34+
this._mintInfo = info;
35+
if (info.nuts[22]) {
36+
this._protectedEnpoints = {
37+
cache: {},
38+
apiReturn: info.nuts[22].protected_endpoints.map((o) => ({
39+
method: o.method,
40+
regex: new RegExp(o.path),
41+
})),
42+
};
43+
}
44+
}
45+
46+
isSupported(num: 4 | 5): { disabled: boolean; params: SwapMethod[] };
47+
isSupported(num: 7 | 8 | 9 | 10 | 11 | 12 | 14 | 20): { supported: boolean };
48+
isSupported(num: 17): { supported: boolean; params?: WebSocketSupport[] };
49+
isSupported(num: 15): { supported: boolean; params?: MPPMethod[] };
50+
isSupported(num: number) {
51+
switch (num) {
52+
case 4:
53+
case 5: {
54+
return this.checkMintMelt(num);
55+
}
56+
case 7:
57+
case 8:
58+
case 9:
59+
case 10:
60+
case 11:
61+
case 12:
62+
case 14:
63+
case 20: {
64+
return this.checkGenericNut(num);
65+
}
66+
case 17: {
67+
return this.checkNut17();
68+
}
69+
case 15: {
70+
return this.checkNut15();
71+
}
72+
default: {
73+
throw new Error('nut is not supported by cashu-ts');
74+
}
75+
}
76+
}
77+
78+
requiresBlindAuthToken(path: string) {
79+
if (!this._protectedEnpoints) {
80+
return false;
81+
}
82+
if (typeof this._protectedEnpoints.cache[path] === 'boolean') {
83+
return this._protectedEnpoints.cache[path];
84+
}
85+
const isProtectedEndpoint = this._protectedEnpoints.apiReturn.some((e) =>
86+
e.regex.test(path),
87+
);
88+
this._protectedEnpoints.cache[path] = isProtectedEndpoint;
89+
return isProtectedEndpoint;
90+
}
91+
92+
private checkGenericNut(num: 7 | 8 | 9 | 10 | 11 | 12 | 14 | 20) {
93+
if (this._mintInfo.nuts[num]?.supported) {
94+
return { supported: true };
95+
}
96+
return { supported: false };
97+
}
98+
private checkMintMelt(num: 4 | 5) {
99+
const mintMeltInfo = this._mintInfo.nuts[num];
100+
if (
101+
mintMeltInfo &&
102+
mintMeltInfo.methods.length > 0 &&
103+
!mintMeltInfo.disabled
104+
) {
105+
return { disabled: false, params: mintMeltInfo.methods };
106+
}
107+
return { disabled: true, params: mintMeltInfo.methods };
108+
}
109+
private checkNut17() {
110+
if (
111+
this._mintInfo.nuts[17] &&
112+
this._mintInfo.nuts[17].supported.length > 0
113+
) {
114+
return { supported: true, params: this._mintInfo.nuts[17].supported };
115+
}
116+
return { supported: false };
117+
}
118+
private checkNut15() {
119+
if (this._mintInfo.nuts[15] && this._mintInfo.nuts[15].methods.length > 0) {
120+
return { supported: true, params: this._mintInfo.nuts[15].methods };
121+
}
122+
return { supported: false };
123+
}
124+
125+
get contact() {
126+
return this._mintInfo.contact;
127+
}
128+
129+
get description() {
130+
return this._mintInfo.description;
131+
}
132+
133+
get description_long() {
134+
return this._mintInfo.description_long;
135+
}
136+
137+
get name() {
138+
return this._mintInfo.name;
139+
}
140+
141+
get pubkey() {
142+
return this._mintInfo.pubkey;
143+
}
144+
145+
get nuts() {
146+
return this._mintInfo.nuts;
147+
}
148+
149+
get version() {
150+
return this._mintInfo.version;
151+
}
152+
153+
get motd() {
154+
return this._mintInfo.motd;
155+
}
156+
157+
// Below methods are added in addition to what the cashu-ts MintInfo class provides
158+
159+
get iconUrl() {
160+
return this._mintInfo.icon_url;
161+
}
162+
163+
/**
164+
* Whether the mint only allows internal melts.
165+
*
166+
* NOTE: This flag is not currently defined in the NUTs.
167+
* Internal melts only is a feature that we have added to agicash mints
168+
* for creating a closed-loop mint.
169+
*/
170+
get internalMeltsOnly() {
171+
const methods = this._mintInfo.nuts[5].methods as (SwapMethod & {
172+
options?: { internal_melts_only?: boolean };
173+
})[];
174+
return methods.some(
175+
(method) => method.options?.internal_melts_only === true,
176+
);
177+
}
178+
}

app/lib/cashu/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,5 +3,5 @@ export * from './secret';
33
export * from './token';
44
export * from './utils';
55
export * from './error-codes';
6-
export type { MintInfo } from './types';
76
export * from './payment-request';
7+
export * from './extended-mint-info';

app/lib/cashu/mint-validation.ts

Lines changed: 8 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,14 @@
11
import type { MintKeyset, WebSocketSupport } from '@cashu/cashu-ts';
2-
import type {
3-
CashuProtocolUnit,
4-
MintInfo,
5-
NUT,
6-
NUT17WebSocketCommand,
7-
} from './types';
2+
import type { ExtendedMintInfo } from './extended-mint-info';
3+
import type { CashuProtocolUnit, NUT, NUT17WebSocketCommand } from './types';
84

95
type NutValidationResult =
106
| { isValid: false; message: string }
117
| { isValid: true };
128

139
type NutValidation = {
1410
nut: NUT;
15-
validate: (info: MintInfo, unit: string) => NutValidationResult;
11+
validate: (info: ExtendedMintInfo, unit: string) => NutValidationResult;
1612
};
1713

1814
type BuildMintValidatorOptions = {
@@ -36,7 +32,7 @@ export const buildMintValidator = (params: BuildMintValidatorOptions) => {
3632
return (
3733
mintUrl: string,
3834
selectedUnit: CashuProtocolUnit,
39-
mintInfo: MintInfo,
35+
mintInfo: ExtendedMintInfo,
4036
keysets: MintKeyset[],
4137
): string | true => {
4238
if (!/^https?:\/\/.+/.test(mintUrl)) {
@@ -155,7 +151,7 @@ const createNutValidators = ({
155151
};
156152

157153
const validateBolt11Support = (
158-
info: MintInfo,
154+
info: ExtendedMintInfo,
159155
operation: 'minting' | 'melting',
160156
unit: string,
161157
): NutValidationResult => {
@@ -184,7 +180,7 @@ const validateBolt11Support = (
184180
};
185181

186182
const validateGenericNut = (
187-
info: MintInfo,
183+
info: ExtendedMintInfo,
188184
nut: Extract<NUT, 7 | 8 | 9 | 10 | 11 | 12 | 20>,
189185
message: string,
190186
): NutValidationResult => {
@@ -199,7 +195,7 @@ const validateGenericNut = (
199195
};
200196

201197
const validateWebSocketSupport = (
202-
info: MintInfo,
198+
info: ExtendedMintInfo,
203199
unit: string,
204200
requiredCommands: NUT17WebSocketCommand[],
205201
): NutValidationResult => {
@@ -229,7 +225,7 @@ const validateWebSocketSupport = (
229225
};
230226

231227
const validateMintFeatures = (
232-
mintInfo: MintInfo,
228+
mintInfo: ExtendedMintInfo,
233229
unit: string,
234230
nutValidators: NutValidation[],
235231
): NutValidationResult => {

app/lib/cashu/types.ts

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import type { CashuWallet } from '@cashu/cashu-ts';
21
import { z } from 'zod';
32

43
/**
@@ -140,12 +139,6 @@ export type ProofSecret =
140139
*/
141140
export type P2PKSecret = NUT10Secret & { kind: 'P2PK' };
142141

143-
/**
144-
* A class that represents the data fetched from the mint's
145-
* [NUT-06 info endpoint](https://github.com/cashubtc/nuts/blob/main/06.md)
146-
*/
147-
export type MintInfo = Awaited<ReturnType<CashuWallet['getMintInfo']>>;
148-
149142
/**
150143
* The units that are determined by the soft-consensus of cashu mints and wallets.
151144
* These units are not definite as they are not defined in NUTs directly.

app/lib/cashu/utils.ts

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import Big from 'big.js';
99
import type { DistributedOmit } from 'type-fest';
1010
import { decodeBolt11 } from '~/lib/bolt11';
1111
import type { Currency, CurrencyUnit } from '../money';
12+
import { ExtendedMintInfo } from './extended-mint-info';
1213
import { sumProofs } from './proof';
1314
import type { CashuProtocolUnit } from './types';
1415

@@ -83,13 +84,17 @@ export const getWalletCurrency = (wallet: CashuWallet) => {
8384
*/
8485
export class ExtendedCashuWallet extends CashuWallet {
8586
private _bip39Seed: Uint8Array | undefined;
87+
private _extendedMintInfo: ExtendedMintInfo | undefined;
8688

8789
constructor(
8890
mint: CashuMint,
89-
options: ConstructorParameters<typeof CashuWallet>[1],
91+
options?: ConstructorParameters<typeof CashuWallet>[1] & {
92+
mintInfo?: ExtendedMintInfo;
93+
},
9094
) {
9195
super(mint, options);
9296
this._bip39Seed = options?.bip39seed;
97+
this._extendedMintInfo = options?.mintInfo;
9398
}
9499

95100
get seed() {
@@ -156,6 +161,15 @@ export class ExtendedCashuWallet extends CashuWallet {
156161
return fee;
157162
}
158163

164+
async getExtendedMintInfo() {
165+
if (this._extendedMintInfo) {
166+
return this._extendedMintInfo;
167+
}
168+
const info = new ExtendedMintInfo(await this.mint.getInfo());
169+
this._extendedMintInfo = info;
170+
return info;
171+
}
172+
159173
private getMinNumberOfProofsForAmount(keys: Keys, amount: Big) {
160174
const availableDenominations = Object.keys(keys).map((x) => new Big(x));
161175
const biggestDenomination = availableDenominations.reduce(
@@ -205,6 +219,7 @@ export const getCashuWallet = (
205219
'unit'
206220
> & {
207221
unit?: CurrencyUnit;
222+
mintInfo?: ExtendedMintInfo;
208223
} = {},
209224
) => {
210225
const { unit, ...rest } = options;

0 commit comments

Comments
 (0)