Files
sattle/src/lnurlcash/ops/receiveBearer.ts
T

82 lines
2.7 KiB
TypeScript

// Receiving a bearer note: wraps receive.ts's receiveNote +
// secureReceivedNote into one flow - resolve whatever came in (note URL,
// bech32, lnurlw://), verify it with the issuing service, then rotate
// immediately, since the previous holder (and anything that logged the URL
// in transit) still knows the old secret.
import {
AmbiguousMutationError,
NoteSpentError,
NoteUnknownError,
PendingNoteError,
probeBurnedNote,
withNewK1
} from 'lnurlcash-kit'
import type {LnurlcashOptions} from 'lnurlcash-kit'
import type {Bearer, NewBearer} from '../types'
import {receiveNote, secureReceivedNote} from '../receive'
import type {ClaimedNote} from './mint'
// NoteSpentError / NoteUnknownError / PendingNoteError from the service are
// definitive and propagate; an unreachable service still yields the note,
// unverified, at the sender's declared amount.
export const receiveBearer = async (
input: string,
existing: Bearer[],
options: LnurlcashOptions = {}
): Promise<ClaimedNote> => {
const note = await receiveNote(input, existing)
if (!note.verified || !note.callback) {
return {note, rotated: false}
}
try {
const rotatedUrl = await secureReceivedNote(note)
return {note: {...note, url: rotatedUrl}, rotated: true}
} catch (err) {
// a definitive service state (dead/unknown/locked mid-melt) is not a
// rotation failure to warn about - it tells the holder what this note
// actually is, so it propagates distinctly
if (
err instanceof NoteSpentError ||
err instanceof PendingNoteError ||
err instanceof NoteUnknownError
) {
throw err
}
if (err instanceof AmbiguousMutationError) {
const outcome = await probeBurnedNote(note.url, options)
if (outcome === 'gone') {
return {
note: {
...note,
url: withNewK1(note.url, err.newSecrets[0], note.amount)
},
rotated: true
}
}
if (outcome === 'unknown') {
const possibleCopy: NewBearer = {
url: withNewK1(note.url, err.newSecrets[0], note.amount),
callback: note.callback,
amount: note.amount,
verified: false
}
if (note.mintPubkey) possibleCopy.mintPubkey = note.mintPubkey
return {
note,
rotated: false,
possibleCopy,
rotationError: `${err.message} The rotation may still have gone through - the possible rotated copy is tracked unverified alongside this one.`
}
}
}
// a failed rotate never fails the receive - the note is money as it
// is; the caller warns that it must be treated as exposed
return {
note,
rotated: false,
rotationError: err instanceof Error ? err.message : String(err)
}
}
}