mirror of
https://github.com/tompro/sattle.git
synced 2026-08-27 07:15:59 +00:00
feat: wallet operations engine and encrypted storage with tests
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user