mirror of
https://github.com/tompro/sattle.git
synced 2026-08-27 07:15:59 +00:00
fix: fence mint and receive operations
This commit is contained in:
@@ -0,0 +1,106 @@
|
|||||||
|
import {describe, expect, it} from 'vitest'
|
||||||
|
import {
|
||||||
|
NoteSpentError,
|
||||||
|
PendingNoteError,
|
||||||
|
buildNoteUrl,
|
||||||
|
fetchNoteInfo,
|
||||||
|
meltNote,
|
||||||
|
noteK1,
|
||||||
|
rotateNote,
|
||||||
|
} from 'lnurlcash-kit'
|
||||||
|
|
||||||
|
import {claimMintedNote, prepareMint, receiveBearer} from './ops'
|
||||||
|
import {requiredValue} from './test-utils'
|
||||||
|
import {makeBearer, mint, noteUrl, secret, settleLastInvoice} from './ops.testHarness'
|
||||||
|
|
||||||
|
describe('mint -> claim -> rotate', () => {
|
||||||
|
it('mints a note from a paid invoice and rotates it immediately', async () => {
|
||||||
|
const instance = await mint({testHooks: true})
|
||||||
|
const prepared = await prepareMint(`mint@127.0.0.1:${instance.port}`, 21_000)
|
||||||
|
expect(prepared.invoice).toMatch(/^lnbc/)
|
||||||
|
expect(prepared.verifyUrl).toBeTruthy()
|
||||||
|
expect(prepared.expectedNoteValueMsat).toBe(21_000)
|
||||||
|
const preimage = await settleLastInvoice(instance)
|
||||||
|
const claimed = await claimMintedNote(prepared, {
|
||||||
|
intervalMs: 10,
|
||||||
|
intervalCapMs: 50,
|
||||||
|
maxWaitMs: 5_000,
|
||||||
|
})
|
||||||
|
expect(claimed.rotated).toBe(true)
|
||||||
|
expect(claimed.note.amount).toBe(21_000)
|
||||||
|
expect(claimed.note.verified).toBe(true)
|
||||||
|
expect(instance.state.noteState(preimage)).toBe('burned')
|
||||||
|
const k1 = requiredValue(noteK1(claimed.note.url))
|
||||||
|
expect(k1).not.toBe(preimage)
|
||||||
|
expect(instance.state.noteState(k1)).toBe('outstanding')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('grosses the invoice up for an advertised mint fee', async () => {
|
||||||
|
const instance = await mint({testHooks: true, baseFeeMsat: 1_000, feePpm: 2_000})
|
||||||
|
const prepared = await prepareMint(`mint@127.0.0.1:${instance.port}`, 100_000)
|
||||||
|
expect(prepared.grossMsat).toBeGreaterThan(100_000)
|
||||||
|
const preimage = await settleLastInvoice(instance)
|
||||||
|
const info = await fetchNoteInfo(
|
||||||
|
buildNoteUrl(prepared.withdrawLink, preimage, prepared.expectedNoteValueMsat),
|
||||||
|
)
|
||||||
|
expect(info.maxWithdrawable).toBeGreaterThanOrEqual(99_000)
|
||||||
|
expect(info.maxWithdrawable).toBeLessThanOrEqual(prepared.grossMsat)
|
||||||
|
await expect(
|
||||||
|
claimMintedNote(prepared, {intervalMs: 10, intervalCapMs: 20, maxWaitMs: 500}),
|
||||||
|
).rejects.toThrow(/different invoice/)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('times out cleanly when the invoice is never paid', async () => {
|
||||||
|
const instance = await mint({testHooks: true})
|
||||||
|
const prepared = await prepareMint(`mint@127.0.0.1:${instance.port}`, 21_000)
|
||||||
|
await expect(
|
||||||
|
claimMintedNote(prepared, {intervalMs: 10, intervalCapMs: 20, maxWaitMs: 100}),
|
||||||
|
).rejects.toThrow(/not confirmed/i)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('receiveBearer', () => {
|
||||||
|
it("verifies an incoming note and rotates it, burning the sender's copy", async () => {
|
||||||
|
const instance = await mint()
|
||||||
|
const senderK1 = secret('20')
|
||||||
|
instance.state.creditNote(senderK1, 21_000)
|
||||||
|
const received = await receiveBearer(noteUrl(instance, senderK1, 21_000), [])
|
||||||
|
expect(received.rotated).toBe(true)
|
||||||
|
expect(received.note.amount).toBe(21_000)
|
||||||
|
expect(received.note.verified).toBe(true)
|
||||||
|
const newK1 = requiredValue(noteK1(received.note.url))
|
||||||
|
expect(newK1).not.toBe(senderK1)
|
||||||
|
expect(instance.state.noteState(senderK1)).toBe('burned')
|
||||||
|
expect(instance.state.noteState(newK1)).toBe('outstanding')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('refuses a note the wallet already holds', async () => {
|
||||||
|
const instance = await mint()
|
||||||
|
const senderK1 = secret('21')
|
||||||
|
const existing = await makeBearer(instance, senderK1, 21_000)
|
||||||
|
await expect(receiveBearer(noteUrl(instance, senderK1, 21_000), [existing])).rejects.toThrow(
|
||||||
|
/already/,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('surfaces a spent note as definitively spent', async () => {
|
||||||
|
const instance = await mint()
|
||||||
|
const k1 = secret('22')
|
||||||
|
const bearer = await makeBearer(instance, k1, 21_000)
|
||||||
|
const info = await fetchNoteInfo(bearer.url)
|
||||||
|
await rotateNote(info.callback, k1)
|
||||||
|
await expect(receiveBearer(noteUrl(instance, k1, 21_000), [])).rejects.toBeInstanceOf(
|
||||||
|
NoteSpentError,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('surfaces a note locked mid-melt as pending, not as unverified', async () => {
|
||||||
|
const instance = await mint({meltNeverSettles: true})
|
||||||
|
const k1 = secret('23')
|
||||||
|
const bearer = await makeBearer(instance, k1, 21_000)
|
||||||
|
await meltNote(bearer.callback, k1, 'lnbc21n1pjqrstuvwxyz')
|
||||||
|
await expect(receiveBearer(noteUrl(instance, k1, 21_000), [])).rejects.toBeInstanceOf(
|
||||||
|
PendingNoteError,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
+23
-43
@@ -19,13 +19,14 @@ import {
|
|||||||
rotateNote,
|
rotateNote,
|
||||||
sameInvoice,
|
sameInvoice,
|
||||||
serverOf,
|
serverOf,
|
||||||
withNewK1
|
withNewK1,
|
||||||
} from 'lnurlcash-kit'
|
} from 'lnurlcash-kit'
|
||||||
import type {LnurlcashOptions, MintAddressInfo} from 'lnurlcash-kit'
|
import type {MintAddressInfo} from 'lnurlcash-kit'
|
||||||
import type {NewBearer} from '../types'
|
import type {NewBearer} from '../types'
|
||||||
import {ceilMsatToSat} from '../units'
|
import {ceilMsatToSat} from '../units'
|
||||||
import type {PollOptions} from './shared'
|
import type {PollOptions} from './shared'
|
||||||
import {pollVerifyUntilSettled} from './shared'
|
import type {FundOperationOptions} from './shared'
|
||||||
|
import {assertFundOwner, pollVerifyUntilSettled} from './shared'
|
||||||
|
|
||||||
export type PreparedMint = {
|
export type PreparedMint = {
|
||||||
invoice: string
|
invoice: string
|
||||||
@@ -52,7 +53,7 @@ export type PreparedMint = {
|
|||||||
export const prepareMint = async (
|
export const prepareMint = async (
|
||||||
mintInput: string,
|
mintInput: string,
|
||||||
amountMsat: number,
|
amountMsat: number,
|
||||||
options: LnurlcashOptions = {}
|
options: FundOperationOptions = {},
|
||||||
): Promise<PreparedMint> => {
|
): Promise<PreparedMint> => {
|
||||||
if (!Number.isInteger(amountMsat) || amountMsat <= 0) {
|
if (!Number.isInteger(amountMsat) || amountMsat <= 0) {
|
||||||
throw new Error('Amount must be a positive whole number of msat.')
|
throw new Error('Amount must be a positive whole number of msat.')
|
||||||
@@ -69,21 +70,20 @@ export const prepareMint = async (
|
|||||||
try {
|
try {
|
||||||
nodeInfo = await fetchMintAddress(addressUrl, options)
|
nodeInfo = await fetchMintAddress(addressUrl, options)
|
||||||
payUrl = nodeInfo.payLink
|
payUrl = nodeInfo.payLink
|
||||||
} catch {
|
} catch (error) {
|
||||||
// no mint-address support here - proceed with just the guess
|
// no mint-address support here - proceed with just the guess
|
||||||
|
if (!(error instanceof Error)) throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const info = await fetchPayRequest(payUrl, options)
|
const info = await fetchPayRequest(payUrl, options)
|
||||||
if (!info.withdrawLink) {
|
if (!info.withdrawLink) {
|
||||||
throw new Error(
|
throw new Error('This payRequest does not advertise lnurlcash minting (no withdrawLink).')
|
||||||
'This payRequest does not advertise lnurlcash minting (no withdrawLink).'
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
const grossMsat = ceilMsatToSat(
|
const grossMsat = ceilMsatToSat(
|
||||||
info.mintFee ? grossUpForMintFee(amountMsat, info.mintFee) : amountMsat
|
info.mintFee ? grossUpForMintFee(amountMsat, info.mintFee) : amountMsat,
|
||||||
)
|
)
|
||||||
if (grossMsat < info.minSendable || grossMsat > info.maxSendable) {
|
if (grossMsat < info.minSendable || grossMsat > info.maxSendable) {
|
||||||
throw new Error('Amount is outside this mint\'s sendable range.')
|
throw new Error("Amount is outside this mint's sendable range.")
|
||||||
}
|
}
|
||||||
const invoice = await requestInvoice(info.callback, grossMsat, options)
|
const invoice = await requestInvoice(info.callback, grossMsat, options)
|
||||||
const prepared: PreparedMint = {
|
const prepared: PreparedMint = {
|
||||||
@@ -95,7 +95,7 @@ export const prepareMint = async (
|
|||||||
withdrawLink: info.withdrawLink,
|
withdrawLink: info.withdrawLink,
|
||||||
server: serverOf(payUrl),
|
server: serverOf(payUrl),
|
||||||
username: lightningAddressUsername(payUrl),
|
username: lightningAddressUsername(payUrl),
|
||||||
nodeInfo
|
nodeInfo,
|
||||||
}
|
}
|
||||||
if (info.mintPubkey) prepared.mintPubkey = info.mintPubkey
|
if (info.mintPubkey) prepared.mintPubkey = info.mintPubkey
|
||||||
return prepared
|
return prepared
|
||||||
@@ -123,11 +123,11 @@ export type ClaimedNote = {
|
|||||||
export const claimMintedNote = async (
|
export const claimMintedNote = async (
|
||||||
prepared: PreparedMint,
|
prepared: PreparedMint,
|
||||||
poll: PollOptions = {},
|
poll: PollOptions = {},
|
||||||
options: LnurlcashOptions = {}
|
options: FundOperationOptions = {},
|
||||||
): Promise<ClaimedNote> => {
|
): Promise<ClaimedNote> => {
|
||||||
if (!prepared.verifyUrl) {
|
if (!prepared.verifyUrl) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'This mint did not advertise a verify URL - the invoice cannot be auto-claimed.'
|
'This mint did not advertise a verify URL - the invoice cannot be auto-claimed.',
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const verifyUrl = prepared.verifyUrl
|
const verifyUrl = prepared.verifyUrl
|
||||||
@@ -135,15 +135,11 @@ export const claimMintedNote = async (
|
|||||||
// a settled report only means this wallet's invoice was paid if it's for
|
// a settled report only means this wallet's invoice was paid if it's for
|
||||||
// the invoice this wallet actually requested
|
// the invoice this wallet actually requested
|
||||||
if (!sameInvoice(result.pr, prepared.invoice)) {
|
if (!sameInvoice(result.pr, prepared.invoice)) {
|
||||||
throw new Error(
|
throw new Error("The service's verify response is for a different invoice than requested.")
|
||||||
"The service's verify response is for a different invoice than requested."
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
const preimage = result.preimage
|
const preimage = result.preimage
|
||||||
if (!preimage || !isPreimage(preimage)) {
|
if (!preimage || !isPreimage(preimage)) {
|
||||||
throw new Error(
|
throw new Error('The payment settled but the service did not reveal the preimage.')
|
||||||
'The payment settled but the service did not reveal the preimage.'
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
return claimFromPreimage(prepared, preimage, options)
|
return claimFromPreimage(prepared, preimage, options)
|
||||||
}
|
}
|
||||||
@@ -168,15 +164,12 @@ export type ClaimTarget = {
|
|||||||
export const claimFromPreimage = async (
|
export const claimFromPreimage = async (
|
||||||
claim: ClaimTarget,
|
claim: ClaimTarget,
|
||||||
preimage: string,
|
preimage: string,
|
||||||
options: LnurlcashOptions = {}
|
options: FundOperationOptions = {},
|
||||||
): Promise<ClaimedNote> => {
|
): Promise<ClaimedNote> => {
|
||||||
// declare the invoiced amount (a claim - not yet confirmed) so the note
|
// declare the invoiced amount (a claim - not yet confirmed) so the note
|
||||||
// is self-describing even before the verifying GET below
|
// is self-describing even before the verifying GET below
|
||||||
const declaredUrl = buildNoteUrl(
|
const declaredUrl = buildNoteUrl(claim.withdrawLink, preimage, claim.expectedNoteValueMsat)
|
||||||
claim.withdrawLink,
|
assertFundOwner(options)
|
||||||
preimage,
|
|
||||||
claim.expectedNoteValueMsat
|
|
||||||
)
|
|
||||||
// the service's maxWithdrawable is authoritative - SERVICE's own fee
|
// the service's maxWithdrawable is authoritative - SERVICE's own fee
|
||||||
// math might not match this wallet's estimate, and the note is worth
|
// math might not match this wallet's estimate, and the note is worth
|
||||||
// exactly maxWithdrawable regardless
|
// exactly maxWithdrawable regardless
|
||||||
@@ -186,7 +179,7 @@ export const claimFromPreimage = async (
|
|||||||
url: withNewK1(declaredUrl, noteInfo.k1, noteInfo.maxWithdrawable),
|
url: withNewK1(declaredUrl, noteInfo.k1, noteInfo.maxWithdrawable),
|
||||||
callback: noteInfo.callback,
|
callback: noteInfo.callback,
|
||||||
amount: noteInfo.maxWithdrawable,
|
amount: noteInfo.maxWithdrawable,
|
||||||
verified: true
|
verified: true,
|
||||||
}
|
}
|
||||||
if (mintPubkey) base.mintPubkey = mintPubkey
|
if (mintPubkey) base.mintPubkey = mintPubkey
|
||||||
|
|
||||||
@@ -196,12 +189,7 @@ export const claimFromPreimage = async (
|
|||||||
let rotationError: string | undefined
|
let rotationError: string | undefined
|
||||||
try {
|
try {
|
||||||
const rotatedNote = await rotateNote(noteInfo.callback, noteInfo.k1, options)
|
const rotatedNote = await rotateNote(noteInfo.callback, noteInfo.k1, options)
|
||||||
url = withNewK1(
|
url = withNewK1(declaredUrl, rotatedNote.k1, noteInfo.maxWithdrawable, rotatedNote.signature)
|
||||||
declaredUrl,
|
|
||||||
rotatedNote.k1,
|
|
||||||
noteInfo.maxWithdrawable,
|
|
||||||
rotatedNote.signature
|
|
||||||
)
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
rotated = false
|
rotated = false
|
||||||
if (err instanceof AmbiguousMutationError) {
|
if (err instanceof AmbiguousMutationError) {
|
||||||
@@ -210,24 +198,16 @@ export const claimFromPreimage = async (
|
|||||||
const outcome = await probeBurnedNote(declaredUrl, options)
|
const outcome = await probeBurnedNote(declaredUrl, options)
|
||||||
if (outcome === 'gone') {
|
if (outcome === 'gone') {
|
||||||
// the burn landed - adopt the fresh secret as the note
|
// the burn landed - adopt the fresh secret as the note
|
||||||
url = withNewK1(
|
url = withNewK1(declaredUrl, err.newSecrets[0], noteInfo.maxWithdrawable)
|
||||||
declaredUrl,
|
|
||||||
err.newSecrets[0],
|
|
||||||
noteInfo.maxWithdrawable
|
|
||||||
)
|
|
||||||
rotated = true
|
rotated = true
|
||||||
} else if (outcome === 'unknown') {
|
} else if (outcome === 'unknown') {
|
||||||
// can't tell: the preimage note is returned either way - the
|
// can't tell: the preimage note is returned either way - the
|
||||||
// possible rotated copy goes alongside it, both refreshable
|
// possible rotated copy goes alongside it, both refreshable
|
||||||
possibleCopy = {
|
possibleCopy = {
|
||||||
url: withNewK1(
|
url: withNewK1(declaredUrl, err.newSecrets[0], noteInfo.maxWithdrawable),
|
||||||
declaredUrl,
|
|
||||||
err.newSecrets[0],
|
|
||||||
noteInfo.maxWithdrawable
|
|
||||||
),
|
|
||||||
callback: noteInfo.callback,
|
callback: noteInfo.callback,
|
||||||
amount: noteInfo.maxWithdrawable,
|
amount: noteInfo.maxWithdrawable,
|
||||||
verified: false
|
verified: false,
|
||||||
}
|
}
|
||||||
if (mintPubkey) possibleCopy.mintPubkey = mintPubkey
|
if (mintPubkey) possibleCopy.mintPubkey = mintPubkey
|
||||||
rotationError = `${err.message} The rotation may still have gone through - the possible rotated copy is tracked unverified alongside this one.`
|
rotationError = `${err.message} The rotation may still have gone through - the possible rotated copy is tracked unverified alongside this one.`
|
||||||
|
|||||||
@@ -10,12 +10,13 @@ import {
|
|||||||
NoteUnknownError,
|
NoteUnknownError,
|
||||||
PendingNoteError,
|
PendingNoteError,
|
||||||
probeBurnedNote,
|
probeBurnedNote,
|
||||||
withNewK1
|
withNewK1,
|
||||||
} from 'lnurlcash-kit'
|
} from 'lnurlcash-kit'
|
||||||
import type {LnurlcashOptions} from 'lnurlcash-kit'
|
|
||||||
import type {Bearer, NewBearer} from '../types'
|
import type {Bearer, NewBearer} from '../types'
|
||||||
import {receiveNote, secureReceivedNote} from '../receive'
|
import {receiveNote, secureReceivedNote} from '../receive'
|
||||||
import type {ClaimedNote} from './mint'
|
import type {ClaimedNote} from './mint'
|
||||||
|
import type {FundOperationOptions} from './shared'
|
||||||
|
import {assertFundOwner} from './shared'
|
||||||
|
|
||||||
// NoteSpentError / NoteUnknownError / PendingNoteError from the service are
|
// NoteSpentError / NoteUnknownError / PendingNoteError from the service are
|
||||||
// definitive and propagate; an unreachable service still yields the note,
|
// definitive and propagate; an unreachable service still yields the note,
|
||||||
@@ -23,13 +24,14 @@ import type {ClaimedNote} from './mint'
|
|||||||
export const receiveBearer = async (
|
export const receiveBearer = async (
|
||||||
input: string,
|
input: string,
|
||||||
existing: Bearer[],
|
existing: Bearer[],
|
||||||
options: LnurlcashOptions = {}
|
options: FundOperationOptions = {},
|
||||||
): Promise<ClaimedNote> => {
|
): Promise<ClaimedNote> => {
|
||||||
const note = await receiveNote(input, existing)
|
const note = await receiveNote(input, existing)
|
||||||
if (!note.verified || !note.callback) {
|
if (!note.verified || !note.callback) {
|
||||||
return {note, rotated: false}
|
return {note, rotated: false}
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
|
assertFundOwner(options)
|
||||||
const rotatedUrl = await secureReceivedNote(note)
|
const rotatedUrl = await secureReceivedNote(note)
|
||||||
return {note: {...note, url: rotatedUrl}, rotated: true}
|
return {note: {...note, url: rotatedUrl}, rotated: true}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -49,9 +51,9 @@ export const receiveBearer = async (
|
|||||||
return {
|
return {
|
||||||
note: {
|
note: {
|
||||||
...note,
|
...note,
|
||||||
url: withNewK1(note.url, err.newSecrets[0], note.amount)
|
url: withNewK1(note.url, err.newSecrets[0], note.amount),
|
||||||
},
|
},
|
||||||
rotated: true
|
rotated: true,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (outcome === 'unknown') {
|
if (outcome === 'unknown') {
|
||||||
@@ -59,14 +61,14 @@ export const receiveBearer = async (
|
|||||||
url: withNewK1(note.url, err.newSecrets[0], note.amount),
|
url: withNewK1(note.url, err.newSecrets[0], note.amount),
|
||||||
callback: note.callback,
|
callback: note.callback,
|
||||||
amount: note.amount,
|
amount: note.amount,
|
||||||
verified: false
|
verified: false,
|
||||||
}
|
}
|
||||||
if (note.mintPubkey) possibleCopy.mintPubkey = note.mintPubkey
|
if (note.mintPubkey) possibleCopy.mintPubkey = note.mintPubkey
|
||||||
return {
|
return {
|
||||||
note,
|
note,
|
||||||
rotated: false,
|
rotated: false,
|
||||||
possibleCopy,
|
possibleCopy,
|
||||||
rotationError: `${err.message} The rotation may still have gone through - the possible rotated copy is tracked unverified alongside this one.`
|
rotationError: `${err.message} The rotation may still have gone through - the possible rotated copy is tracked unverified alongside this one.`,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -75,7 +77,7 @@ export const receiveBearer = async (
|
|||||||
return {
|
return {
|
||||||
note,
|
note,
|
||||||
rotated: false,
|
rotated: false,
|
||||||
rotationError: err instanceof Error ? err.message : String(err)
|
rotationError: err instanceof Error ? err.message : String(err),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ import {
|
|||||||
withNewK1,
|
withNewK1,
|
||||||
NoteSpentError,
|
NoteSpentError,
|
||||||
NoteUnknownError,
|
NoteUnknownError,
|
||||||
PendingNoteError
|
PendingNoteError,
|
||||||
} from 'lnurlcash-kit'
|
} from 'lnurlcash-kit'
|
||||||
import type {Bearer, NewBearer} from './types'
|
import type {Bearer, NewBearer} from './types'
|
||||||
|
|
||||||
@@ -17,20 +17,13 @@ import type {Bearer, NewBearer} from './types'
|
|||||||
// always puts k1 on the wire, so receive.ts's caller should rotate right
|
// always puts k1 on the wire, so receive.ts's caller should rotate right
|
||||||
// after, see secureReceivedNote). Returns the note even when the info fetch
|
// after, see secureReceivedNote). Returns the note even when the info fetch
|
||||||
// fails - a bearer is better stored unverified than dropped.
|
// fails - a bearer is better stored unverified than dropped.
|
||||||
export const receiveNote = async (
|
export const receiveNote = async (input: string, existing: Bearer[]): Promise<NewBearer> => {
|
||||||
input: string,
|
|
||||||
existing: Bearer[]
|
|
||||||
): Promise<NewBearer> => {
|
|
||||||
const url = resolveNoteInput(input)
|
const url = resolveNoteInput(input)
|
||||||
if (!url) {
|
if (!url) {
|
||||||
throw new Error('Not an LNURLcash bearer note (needs a k1).')
|
throw new Error('Not an LNURLcash bearer note (needs a k1).')
|
||||||
}
|
}
|
||||||
const k1 = noteK1(url)
|
const k1 = noteK1(url)
|
||||||
if (
|
if (existing.some((b) => noteK1(b.url) === k1 && serverOf(b.url) === serverOf(url))) {
|
||||||
existing.some(
|
|
||||||
b => noteK1(b.url) === k1 && serverOf(b.url) === serverOf(url)
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
throw new Error('This note is already in your wallet.')
|
throw new Error('This note is already in your wallet.')
|
||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
@@ -40,7 +33,7 @@ export const receiveNote = async (
|
|||||||
callback: info.callback,
|
callback: info.callback,
|
||||||
amount: info.maxWithdrawable,
|
amount: info.maxWithdrawable,
|
||||||
verified: true,
|
verified: true,
|
||||||
mintPubkey: info.mintPubkey
|
mintPubkey: info.mintPubkey,
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// the service positively told us this k1 is dead, unknown, or locked
|
// the service positively told us this k1 is dead, unknown, or locked
|
||||||
@@ -62,7 +55,7 @@ export const receiveNote = async (
|
|||||||
url,
|
url,
|
||||||
callback: '',
|
callback: '',
|
||||||
amount: noteDeclaredAmount(url) ?? 0,
|
amount: noteDeclaredAmount(url) ?? 0,
|
||||||
verified: false
|
verified: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user