diff --git a/__tests__/mintCapabilities.test.ts b/__tests__/mintCapabilities.test.ts index 77a4a246..9771c710 100644 --- a/__tests__/mintCapabilities.test.ts +++ b/__tests__/mintCapabilities.test.ts @@ -57,6 +57,8 @@ type MethodEntry = { const mintWith = (opts: { mintMethods?: MethodEntry[] meltMethods?: MethodEntry[] + mintDisabled?: boolean + meltDisabled?: boolean nut20?: boolean }) => MintModel.create({ @@ -67,8 +69,8 @@ const mintWith = (opts: { version: 'test/1', contact: [], nuts: { - '4': {methods: opts.mintMethods ?? [], disabled: false}, - '5': {methods: opts.meltMethods ?? [], disabled: false}, + '4': {methods: opts.mintMethods ?? [], disabled: opts.mintDisabled ?? false}, + '5': {methods: opts.meltMethods ?? [], disabled: opts.meltDisabled ?? false}, ...(opts.nut20 === undefined ? {} : {'20': {supported: opts.nut20}}), }, time: Math.floor(Date.now() / 1000), @@ -167,6 +169,53 @@ describe('supportsMint / supportsMelt', () => { }) }) +/** + * A mint may advertise methods AND switch the whole NUT off — nutshell does this + * when its backend is unavailable. The flag wins over the list, or the wallet + * offers a flow the mint refuses at quote time. + */ +describe('the NUT-04 / NUT-05 disabled flag', () => { + it('reports no mint method when NUT-04 is disabled, however many are listed', () => { + const mint = mintWith({ + mintMethods: [BOLT11_SAT, ONCHAIN_SAT], + mintDisabled: true, + nut20: true, + }) + + expect(mint.mintMethodSetting('bolt11', 'sat')).toBeUndefined() + expect(mint.mintMethodSetting('onchain', 'sat')).toBeUndefined() + expect(mint.supportsMint('bolt11', 'sat')).toBe(false) + expect(mint.supportsMint('onchain', 'sat')).toBe(false) + }) + + it('reports no melt method when NUT-05 is disabled', () => { + const mint = mintWith({meltMethods: [BOLT11_SAT], meltDisabled: true}) + + expect(mint.meltMethodSetting('bolt11', 'sat')).toBeUndefined() + expect(mint.supportsMelt('bolt11', 'sat')).toBe(false) + }) + + it('disables only the half that is switched off', () => { + // topups closed, payouts still open + const mint = mintWith({ + mintMethods: [BOLT11_SAT], + mintDisabled: true, + meltMethods: [BOLT11_SAT], + }) + + expect(mint.supportsMint('bolt11', 'sat')).toBe(false) + expect(mint.supportsMelt('bolt11', 'sat')).toBe(true) + }) + + it('is not the same as unknown capabilities — a disabled mint has cached info', () => { + // the two resolve OPPOSITELY for bolt11: unknown assumes it, disabled refuses it + const mint = mintWith({mintMethods: [BOLT11_SAT], mintDisabled: true}) + + expect(mint.hasUnknownCapabilities).toBe(false) + expect(mint.supportsMint('bolt11', 'sat')).toBe(false) + }) +}) + describe('unknown capabilities (info never cached)', () => { it('assumes bolt11, so an upgrading user never loses their Lightning options', () => { const mint = mintWithUnknownInfo() diff --git a/package.json b/package.json index 95a4b86e..f2770c64 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "minibits_wallet", - "version": "0.4.3-beta.23", + "version": "0.4.3-beta.25", "private": true, "scripts": { "android:clean": "cd android && ./gradlew clean", diff --git a/src/models/Mint.ts b/src/models/Mint.ts index a9625b21..8cacba38 100644 --- a/src/models/Mint.ts +++ b/src/models/Mint.ts @@ -204,16 +204,23 @@ export const MintModel = types * * The setting also carries `min_amount` / `max_amount` (and, for onchain, * `options.confirmations`), which callers need for limit checks. + * + * Each NUT carries a `disabled` flag beside its method list, and a mint that + * advertises methods while disabling the NUT is telling us that direction is + * off — nutshell sets it when its backend is unavailable. Ignoring the flag + * means offering a flow the mint then refuses. Same rule as `getMethods` in + * screens/Mints/mintInfoSummary.ts, which drives MintInfoScreen: the two must + * stay in step or the mint's detail screen contradicts the topup menu. */ mintMethodSetting(method: PaymentMethod, unit: MintUnit): SwapMethod | undefined { - return self.mintInfo?.nuts?.['4']?.methods?.find( - m => m.method === method && m.unit === unit, - ) + const nut4 = self.mintInfo?.nuts?.['4'] + if (nut4?.disabled === true) return undefined + return nut4?.methods?.find(m => m.method === method && m.unit === unit) }, meltMethodSetting(method: PaymentMethod, unit: MintUnit): SwapMethod | undefined { - return self.mintInfo?.nuts?.['5']?.methods?.find( - m => m.method === method && m.unit === unit, - ) + const nut5 = self.mintInfo?.nuts?.['5'] + if (nut5?.disabled === true) return undefined + return nut5?.methods?.find(m => m.method === method && m.unit === unit) }, /** Does the mint advertise NUT-20 (signed mint quotes)? */ get supportsNut20(): boolean {