mirror of
https://github.com/minibits-cash/minibits_wallet.git
synced 2026-10-05 19:28:26 +00:00
NFC working prototype + stub for awaitable wallet tasks
This commit is contained in:
+1
-1
@@ -21,7 +21,7 @@
|
||||
"machine-translate": "npx @inlang/cli machine translate --project minibits.inlang"
|
||||
},
|
||||
"dependencies": {
|
||||
"@cashu/cashu-ts": "minibits-cash/cashu-ts#c85d4638c007ac8126bc83a7287a6d848fcc8153",
|
||||
"@cashu/cashu-ts": "minibits-cash/cashu-ts#e97b1a4ee9a7ddbb1468302c6996a9d6d3efcb6f",
|
||||
"@fortawesome/fontawesome-svg-core": "^7.1.0",
|
||||
"@fortawesome/free-brands-svg-icons": "^7.1.0",
|
||||
"@fortawesome/free-regular-svg-icons": "^7.1.0",
|
||||
|
||||
+210
-393
@@ -47,6 +47,7 @@ import Animated, {
|
||||
useAnimatedStyle,
|
||||
Easing,
|
||||
} from 'react-native-reanimated';
|
||||
import { NfcService } from '../services/nfcService'
|
||||
//import Animated from 'react-native-reanimated'
|
||||
|
||||
const ContactlessIcon = (color: ColorValue | string) => `<?xml version="1.0" encoding="utf-8"?>
|
||||
@@ -113,6 +114,7 @@ export const NfcPayScreen = observer(function NfcPayScreen({ route }: Props) {
|
||||
const navigation = useNavigation<any>()
|
||||
const { mintsStore, walletStore, proofsStore } = useStores()
|
||||
const unitRef = useRef<MintUnit>('sat')
|
||||
|
||||
const isInternetReachable = useIsInternetReachable()
|
||||
|
||||
const [encodedTokenToSend, setEncodedTokenToSend] = useState<string | undefined>()
|
||||
@@ -132,8 +134,6 @@ export const NfcPayScreen = observer(function NfcPayScreen({ route }: Props) {
|
||||
const [resultModalInfo, setResultModalInfo] = useState<{status: TransactionStatus, title?: string, message: string} | undefined>()
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
|
||||
const [isSendTaskSentToQueue, setIsSendTaskSentToQueue] = useState(false)
|
||||
const [isTransferTaskSentToQueue, setIsTransferTaskSentToQueue] = useState(false)
|
||||
const [isResultModalVisible, setIsResultModalVisible] = useState(false)
|
||||
// NFC state
|
||||
const [isNfcEnabled, setIsNfcEnabled] = useState(false)
|
||||
@@ -150,17 +150,15 @@ export const NfcPayScreen = observer(function NfcPayScreen({ route }: Props) {
|
||||
useEffect(() => {
|
||||
const initNfc = async () => {
|
||||
try {
|
||||
const supported = await NfcManager.isSupported()
|
||||
const supported = await NfcService.init() // runs NfcManager.start()
|
||||
setIsNfcSupported(supported)
|
||||
|
||||
if (!supported) {
|
||||
setNfcInfo('NFC is not supported on this device')
|
||||
return
|
||||
}
|
||||
|
||||
await NfcManager.start()
|
||||
|
||||
const enabled = await NfcManager.isEnabled()
|
||||
|
||||
const enabled = await NfcService.isEnabled()
|
||||
if (!enabled) {
|
||||
setNfcInfo('Please enable NFC in your device settings')
|
||||
return
|
||||
@@ -198,123 +196,10 @@ export const NfcPayScreen = observer(function NfcPayScreen({ route }: Props) {
|
||||
}, [navigation, isNfcEnabled, isProcessing])
|
||||
|
||||
|
||||
const sendTaskListenerRef = useRef<((result: TransactionTaskResult) => void) | null>(null);
|
||||
|
||||
const handleSendTaskResult = useCallback(
|
||||
async (result: TransactionTaskResult) => {
|
||||
log.debug('[NfcScreen] handleSendTaskResult triggered', { result })
|
||||
|
||||
try {
|
||||
const { transaction, error, encodedTokenToSend } = result
|
||||
|
||||
// Always update core transaction state
|
||||
if (transaction) {
|
||||
setTransactionStatus(transaction.status)
|
||||
setTransaction(transaction)
|
||||
setTransactionId(transaction.id)
|
||||
|
||||
// Optional: link back to original payment request
|
||||
if (decodedCashuPaymentRequest?.id || encodedCashuPaymentRequest) {
|
||||
transaction.update({
|
||||
paymentId: decodedCashuPaymentRequest?.id,
|
||||
paymentRequest: encodedCashuPaymentRequest,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ———————— Error Cases ————————
|
||||
if (error || !transaction) {
|
||||
const message = error?.params?.message || error?.message || 'Unknown error'
|
||||
const title = error ? 'Payment failed' : 'Internal error'
|
||||
|
||||
setResultModalInfo({
|
||||
status: (transaction?.status || TransactionStatus.ERROR) as TransactionStatus,
|
||||
title,
|
||||
message,
|
||||
});
|
||||
toggleResultModal()
|
||||
return
|
||||
}
|
||||
|
||||
// ———————— Missing Token ————————
|
||||
if (!encodedTokenToSend) {
|
||||
setResultModalInfo({
|
||||
status: TransactionStatus.ERROR,
|
||||
title: 'Internal error',
|
||||
message: 'Failed to generate ecash token',
|
||||
});
|
||||
toggleResultModal()
|
||||
return
|
||||
}
|
||||
|
||||
// ———————— Success Path ————————
|
||||
setEncodedTokenToSend(encodedTokenToSend)
|
||||
// Send over NFC to payee's POS or wallet
|
||||
await writeTokenToNfcTag(encodedTokenToSend)
|
||||
|
||||
} catch (err: any) {
|
||||
log.error('Unexpected error in handleSendTaskResult', err)
|
||||
|
||||
setResultModalInfo({
|
||||
status: TransactionStatus.ERROR,
|
||||
title: 'Unexpected error',
|
||||
message: err.message || 'Something went wrong',
|
||||
})
|
||||
|
||||
toggleResultModal()
|
||||
}
|
||||
},
|
||||
[
|
||||
decodedCashuPaymentRequest?.id,
|
||||
encodedCashuPaymentRequest,
|
||||
]
|
||||
)
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
if (!isSendTaskSentToQueue) {
|
||||
// optional: make sure no stray listener exists
|
||||
if (sendTaskListenerRef.current) {
|
||||
EventEmitter.off(`ev_${SEND_TASK}_result`, sendTaskListenerRef.current)
|
||||
sendTaskListenerRef.current = null
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const eventName = `ev_${SEND_TASK}_result`
|
||||
|
||||
// Remove previous (defensive)
|
||||
if (sendTaskListenerRef.current) {
|
||||
EventEmitter.off(eventName, sendTaskListenerRef.current)
|
||||
}
|
||||
|
||||
const oneTimeHandler = (result: TransactionTaskResult) => {
|
||||
// Immediately detach so it can never fire twice
|
||||
EventEmitter.off(eventName, oneTimeHandler)
|
||||
sendTaskListenerRef.current = null
|
||||
|
||||
// Forward to the stable callback
|
||||
handleSendTaskResult(result)
|
||||
}
|
||||
|
||||
sendTaskListenerRef.current = oneTimeHandler
|
||||
EventEmitter.on(eventName, oneTimeHandler)
|
||||
|
||||
return () => {
|
||||
if (sendTaskListenerRef.current) {
|
||||
EventEmitter.off(eventName, sendTaskListenerRef.current)
|
||||
sendTaskListenerRef.current = null
|
||||
}
|
||||
}
|
||||
}, [isSendTaskSentToQueue, handleSendTaskResult])
|
||||
|
||||
|
||||
|
||||
|
||||
// Stable handler — only recreated when dependencies actually change
|
||||
const handleSyncStateResult = useCallback(
|
||||
async (result: SyncStateTaskResult) => {
|
||||
log.trace('[NfcScreen] handleSyncStateResult triggered', { result, transactionId });
|
||||
const handleSyncStateResult = useCallback(async (result: SyncStateTaskResult) => {
|
||||
log.trace('[NfcScreen.handleSyncStateResult] handleSyncStateResult triggered', { result, transactionId });
|
||||
|
||||
if (!transactionId) return;
|
||||
|
||||
@@ -322,7 +207,7 @@ export const NfcPayScreen = observer(function NfcPayScreen({ route }: Props) {
|
||||
|
||||
// ——— SUCCESS: Receiver claimed the ecash ———
|
||||
if (completedTransactionIds?.includes(transactionId)) {
|
||||
log.debug('[NfcScreen] Ecash claimed successfully by the payee.', { transactionId });
|
||||
log.debug('[NfcScreen.handleSyncStateResult] Ecash claimed successfully by the payee.', { transactionId });
|
||||
|
||||
const amountSentInt = Math.round(
|
||||
(toNumber(amountToPay || '0') * getCurrency(unitRef.current).precision)
|
||||
@@ -337,6 +222,7 @@ export const NfcPayScreen = observer(function NfcPayScreen({ route }: Props) {
|
||||
});
|
||||
setTransactionStatus(TransactionStatus.COMPLETED);
|
||||
setIsResultModalVisible(true);
|
||||
setNfcInfo('Payment completed successfully!');
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -345,7 +231,7 @@ export const NfcPayScreen = observer(function NfcPayScreen({ route }: Props) {
|
||||
log.trace('[NfcScreen] Sync error for transaction', { transactionId });
|
||||
|
||||
const update = transactionStateUpdates?.find(u => u.tId === transactionId);
|
||||
const message = update?.message || 'Transaction failed to complete on the mint.'
|
||||
const message = update?.message || 'Transaction failed to complete on the mint side.'
|
||||
|
||||
setResultModalInfo({
|
||||
status: TransactionStatus.ERROR,
|
||||
@@ -422,262 +308,72 @@ export const NfcPayScreen = observer(function NfcPayScreen({ route }: Props) {
|
||||
|
||||
// ====================== Lightning Payment (Transfer Task) Result Handler ======================
|
||||
|
||||
const handleTransferTaskResult = useCallback(
|
||||
async (result: TransactionTaskResult) => {
|
||||
log.debug('[NfcScreen] handleTransferTaskResult triggered', {result})
|
||||
|
||||
setIsLoading(false)
|
||||
|
||||
const { transaction, error, message, finalFee } = result
|
||||
|
||||
// ——— Early error before transaction exists ———
|
||||
if (!transaction && error) {
|
||||
setTransactionStatus(TransactionStatus.ERROR)
|
||||
|
||||
setResultModalInfo({
|
||||
status: TransactionStatus.ERROR,
|
||||
title: translate('payCommon_failed'),
|
||||
message: error.message || 'Lightning payment failed',
|
||||
})
|
||||
|
||||
toggleResultModal()
|
||||
return;
|
||||
}
|
||||
|
||||
// ——— Transaction exists ———
|
||||
if (transaction) {
|
||||
const { status } = transaction;
|
||||
setTransactionStatus(status);
|
||||
setTransaction(transaction);
|
||||
|
||||
if (error) {
|
||||
// Pending but timed out / failed
|
||||
if (status === TransactionStatus.PENDING) {
|
||||
setResultModalInfo({
|
||||
status,
|
||||
message,
|
||||
})
|
||||
} else {
|
||||
setResultModalInfo({
|
||||
status,
|
||||
title: error.params?.message ? error.message : translate('payCommon_failed'),
|
||||
message: error.params?.message || error.message || 'Lightning payment failed',
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Success or settled pending
|
||||
setResultModalInfo({
|
||||
status,
|
||||
message: message || 'Lightning payment successful!',
|
||||
title: status === TransactionStatus.COMPLETED ? 'Payment sent!' : undefined,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Fallback (shouldn't happen)
|
||||
setResultModalInfo({
|
||||
status: TransactionStatus.ERROR,
|
||||
title: 'Unknown error',
|
||||
message: 'No transaction data received',
|
||||
})
|
||||
}
|
||||
|
||||
toggleResultModal()
|
||||
},
|
||||
[isTransferTaskSentToQueue],
|
||||
)
|
||||
|
||||
// One-time listener with auto-cleanup
|
||||
const transferTaskListenerRef = useRef<((r: TransactionTaskResult) => void) | null>(null);
|
||||
|
||||
useEffect(() => {
|
||||
if (!isTransferTaskSentToQueue) {
|
||||
if (transferTaskListenerRef.current) {
|
||||
EventEmitter.off(`ev_${TRANSFER_TASK}_result`, transferTaskListenerRef.current)
|
||||
transferTaskListenerRef.current = null
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
const eventName = `ev_${TRANSFER_TASK}_result`
|
||||
|
||||
// Remove any previous listener (defensive)
|
||||
if (transferTaskListenerRef.current) {
|
||||
EventEmitter.off(eventName, transferTaskListenerRef.current)
|
||||
}
|
||||
|
||||
const oneTimeHandler = (result: TransactionTaskResult) => {
|
||||
// Immediately detach — this event should fire only once per payment
|
||||
EventEmitter.off(eventName, oneTimeHandler)
|
||||
transferTaskListenerRef.current = null
|
||||
|
||||
handleTransferTaskResult(result)
|
||||
}
|
||||
|
||||
transferTaskListenerRef.current = oneTimeHandler
|
||||
EventEmitter.on(eventName, oneTimeHandler)
|
||||
|
||||
// Cleanup on unmount or when task flag resets
|
||||
return () => {
|
||||
if (transferTaskListenerRef.current) {
|
||||
EventEmitter.off(eventName, transferTaskListenerRef.current)
|
||||
transferTaskListenerRef.current = null
|
||||
}
|
||||
}
|
||||
}, [handleTransferTaskResult])
|
||||
|
||||
|
||||
|
||||
const readNdefOnce = function () {
|
||||
const cleanUp = () => {
|
||||
NfcManager.setEventListener(NfcEvents.DiscoverTag, null);
|
||||
NfcManager.setEventListener(NfcEvents.SessionClosed, null);
|
||||
};
|
||||
|
||||
return new Promise<TagEvent | null>((resolve) => {
|
||||
let tagFound: TagEvent | null = null;
|
||||
|
||||
NfcManager.setEventListener(NfcEvents.DiscoverTag, (tag: TagEvent) => {
|
||||
tagFound = tag;
|
||||
resolve(tagFound);
|
||||
|
||||
NfcManager.unregisterTagEvent().catch(() => 0);
|
||||
});
|
||||
|
||||
NfcManager.setEventListener(NfcEvents.SessionClosed, (e: any) => {
|
||||
if (e) {
|
||||
log.error(Err.NFC_ERROR, 'NFC Session closed with error', e.message);
|
||||
}
|
||||
|
||||
cleanUp()
|
||||
|
||||
if (!tagFound) {
|
||||
resolve(null)
|
||||
}
|
||||
});
|
||||
|
||||
NfcManager.registerTagEvent();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
const readTag = async function() {
|
||||
try {
|
||||
await NfcManager.requestTechnology([NfcTech.Ndef]);
|
||||
|
||||
const tag = await NfcManager.getTag();
|
||||
return tag
|
||||
|
||||
} catch (e: any) {
|
||||
// for tag reading, we don't actually need to show any error
|
||||
log.error(Err.NFC_ERROR, 'Failed to read NFC tag', e.message);
|
||||
} finally {
|
||||
NfcManager.cancelTechnologyRequest();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const startNfcSession = async () => {
|
||||
let decoded: string | undefined = undefined;
|
||||
try {
|
||||
log.debug('Starting NFC session for payment request reading...', {caller: 'startNfcSession'})
|
||||
log.debug('[startNfcSession] Starting NFC session for payment request reading...')
|
||||
|
||||
const tag = await readNdefOnce()
|
||||
// const tag = await readTag()
|
||||
const tag = await NfcService.readNdefTag()
|
||||
|
||||
if (!tag) {
|
||||
return
|
||||
}
|
||||
|
||||
log.warn('Tag found', {tag});
|
||||
//log.info('NFC Tag found', {tag});
|
||||
const bytes = new Uint8Array(tag.ndefMessage[0]?.payload ?? [])
|
||||
decoded = Ndef.text.decodePayload(bytes)
|
||||
|
||||
/*await NfcManager.requestTechnology(NfcTech.Ndef, {
|
||||
alertMessage: 'Hold phone near the device...',
|
||||
invalidateAfterFirstRead: false,
|
||||
})
|
||||
if(!decoded || decoded.length < 140) {
|
||||
const msg = 'This NFC tag can not be processed, missing or truncated NDEF message.'
|
||||
|
||||
setResultModalInfo({
|
||||
status: TransactionStatus.ERROR,
|
||||
message: msg,
|
||||
})
|
||||
|
||||
const tag = await NfcManager.getTag();
|
||||
log.warn('Tag found', {tag});
|
||||
toggleResultModal()
|
||||
|
||||
if(!tag || !tag.ndefMessage || tag.ndefMessage.length === 0) {
|
||||
setInfo('No NDEF message found on the NFC tag')
|
||||
log.error(Err.NFC_ERROR, msg, { tag })
|
||||
return
|
||||
}
|
||||
|
||||
const bytes = new Uint8Array(
|
||||
tag.ndefMessage[0].payload
|
||||
);
|
||||
setNfcInfo('Processing payment request...')
|
||||
|
||||
let payload: string
|
||||
log.info('NFC tag decoded', { decoded })
|
||||
|
||||
const decoded = Ndef.text.decodePayload(bytes);
|
||||
setReadNfcData(decoded)
|
||||
await handlePaymentRequest(decoded)
|
||||
|
||||
log.warn('NFC tag decoded', { decoded })*/
|
||||
|
||||
|
||||
// setNfcInfo('Reading payment request...')
|
||||
|
||||
// Listen for tag discovery
|
||||
/*NfcManager.setEventListener(NfcEvents.DiscoverTag, async (tag: any) => {
|
||||
if (isProcessing) return
|
||||
setIsProcessing(true)
|
||||
setNfcInfo('Reading payment request...')
|
||||
log.info('NFC tag discovered, reading...', { tag })
|
||||
|
||||
try {
|
||||
const ndefMessage = tag.ndefMessage?.[0]
|
||||
if (!ndefMessage) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'No NDEF message found')
|
||||
}
|
||||
|
||||
let payload: string
|
||||
|
||||
// Handle both Text and URI records
|
||||
if (ndefMessage.type === Ndef.RTD_TEXT) {
|
||||
const decoded = Ndef.text.decodePayload(ndefMessage.payload)
|
||||
payload = decoded
|
||||
} else if (ndefMessage.type === Ndef.RTD_URI) {
|
||||
const decoded = Ndef.uri.decodePayload(ndefMessage.payload)
|
||||
payload = decoded
|
||||
} else {
|
||||
payload = new TextDecoder().decode(ndefMessage.payload)
|
||||
}
|
||||
|
||||
log.debug('NFC tag read', { payload })
|
||||
setReadNfcData(payload)
|
||||
await handlePaymentRequest(payload)
|
||||
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
} finally {
|
||||
setIsProcessing(false)
|
||||
}
|
||||
})*/
|
||||
} catch (e: any) {
|
||||
log.error(Err.NFC_ERROR, e.message)
|
||||
log.error('[NfcPayScreen] handlePaymentRequest failed', {
|
||||
error: e?.message ?? String(e),
|
||||
stack: e?.stack,
|
||||
decoded: decoded?.slice?.(0,200)
|
||||
})
|
||||
handleError(e)
|
||||
} finally {
|
||||
NfcManager.cancelTechnologyRequest().catch(() => {})
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// handlePaymentRequest.ts (main entry)
|
||||
const handlePaymentRequest = async (data: string) => {
|
||||
try {
|
||||
Alert.alert(data)
|
||||
log.info('[handlePaymentRequest] received data via NFC', { data })
|
||||
|
||||
// Alert.alert(data)
|
||||
log.info('[handlePaymentRequest] received data via NFC', { data })
|
||||
|
||||
const result = IncomingParser.findAndExtract(data)
|
||||
if (!result) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Unsupported or invalid payment request')
|
||||
}
|
||||
const result = IncomingParser.findAndExtract(data)
|
||||
|
||||
if (result.type === IncomingDataType.CASHU_PAYMENT_REQUEST) {
|
||||
await handleCashuPaymentRequest(result.encoded)
|
||||
} else if (result.type === IncomingDataType.INVOICE) {
|
||||
await handleLightningInvoice(result.encoded)
|
||||
}
|
||||
} catch (e: any) {
|
||||
handleError(e)
|
||||
log.info('[handlePaymentRequest] parsed result', { result })
|
||||
if (!result || !result.encoded) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Unsupported or invalid payment request')
|
||||
}
|
||||
|
||||
if (result.type === IncomingDataType.CASHU_PAYMENT_REQUEST) {
|
||||
await handleCashuPaymentRequest(result.encoded)
|
||||
} else if (result.type === IncomingDataType.INVOICE) {
|
||||
await handleLightningInvoice(result.encoded)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -718,9 +414,8 @@ export const NfcPayScreen = observer(function NfcPayScreen({ route }: Props) {
|
||||
|
||||
setMintBalanceToSendFrom(selectedBalance)
|
||||
setAmountToPay(formatDisplayAmount(requiredAmount, unit))
|
||||
setIsSendTaskSentToQueue(true);
|
||||
|
||||
WalletTask.sendQueue(
|
||||
|
||||
const result = await WalletTask.sendQueueAwaitable(
|
||||
selectedBalance,
|
||||
requiredAmount,
|
||||
unit,
|
||||
@@ -729,27 +424,105 @@ export const NfcPayScreen = observer(function NfcPayScreen({ route }: Props) {
|
||||
undefined, // p2pk not supported in current Cashu PR spec
|
||||
undefined
|
||||
)
|
||||
|
||||
await handleSendTaskResult(result)
|
||||
|
||||
}
|
||||
|
||||
|
||||
const handleSendTaskResult = async function (result: TransactionTaskResult) {
|
||||
log.debug('[NfcScreen] handleSendTaskResult start', {transactionStatus: result.transaction?.status})
|
||||
|
||||
try {
|
||||
const { transaction, error, encodedTokenToSend } = result
|
||||
|
||||
// Always update core transaction state
|
||||
if (transaction) {
|
||||
setTransactionStatus(transaction.status)
|
||||
setTransaction(transaction)
|
||||
setTransactionId(transaction.id)
|
||||
|
||||
// Optional: link back to original payment request
|
||||
if (decodedCashuPaymentRequest?.id || encodedCashuPaymentRequest) {
|
||||
transaction.update({
|
||||
paymentId: decodedCashuPaymentRequest?.id,
|
||||
paymentRequest: encodedCashuPaymentRequest,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ———————— Error Cases ————————
|
||||
if (error || !transaction) {
|
||||
const message = error?.params?.message || error?.message || 'Unknown error'
|
||||
const title = error ? 'Payment failed' : 'Internal error'
|
||||
|
||||
setResultModalInfo({
|
||||
status: (transaction?.status || TransactionStatus.ERROR) as TransactionStatus,
|
||||
title,
|
||||
message,
|
||||
});
|
||||
toggleResultModal()
|
||||
setNfcInfo('Payment failed')
|
||||
return
|
||||
}
|
||||
|
||||
// ———————— Missing Token ————————
|
||||
if (!encodedTokenToSend) {
|
||||
setResultModalInfo({
|
||||
status: TransactionStatus.ERROR,
|
||||
title: 'Internal error',
|
||||
message: 'Failed to generate ecash token',
|
||||
});
|
||||
toggleResultModal()
|
||||
setNfcInfo('Payment failed')
|
||||
return
|
||||
}
|
||||
|
||||
// ———————— Success Path ————————
|
||||
setEncodedTokenToSend(encodedTokenToSend)
|
||||
// Send over NFC to payee's POS or wallet
|
||||
|
||||
setNfcInfo('Prepared ecash token to be sent via NFC.')
|
||||
|
||||
log.trace('[NfcScreen] handleSendTaskResult: Starting NFC write of an ecash token.')
|
||||
await NfcService.writeNdefMessage(encodedTokenToSend)
|
||||
|
||||
log.trace('[NfcScreen] handleSendTaskResult: Write completed.')
|
||||
setNfcInfo('Ecash token sent successfully.')
|
||||
|
||||
} catch (err: any) {
|
||||
log.error('Error in handleSendTaskResult', {error: err, caller: 'NfcScreen.handleSendTaskResult'})
|
||||
|
||||
setResultModalInfo({
|
||||
status: TransactionStatus.ERROR,
|
||||
title: 'Unexpected error',
|
||||
message: err.message || 'Something went wrong',
|
||||
})
|
||||
|
||||
toggleResultModal()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
const handleLightningInvoice = async (encodedInvoice: string) => {
|
||||
if (!isInternetReachable) {
|
||||
setInfo(translate('commonOfflinePretty'));
|
||||
setInfo(translate('commonOfflinePretty'))
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
const decoded = LightningUtils.decodeInvoice(encodedInvoice)
|
||||
const { amount: invoiceAmount, description, expiry, timestamp } = LightningUtils.getInvoiceData(decoded)
|
||||
|
||||
|
||||
if (!invoiceAmount || invoiceAmount <= 0) {
|
||||
throw new AppError(Err.VALIDATION_ERROR, 'Lightning invoice has no amount')
|
||||
}
|
||||
|
||||
|
||||
const expiresAt = addSeconds(new Date((timestamp as number) * 1000), expiry as number)
|
||||
const feeReserve = Math.max(MIN_LIGHTNING_FEE, Math.round(invoiceAmount * LIGHTNING_FEE_PERCENT / 100))
|
||||
const totalRequired = invoiceAmount + feeReserve;
|
||||
const totalRequired = invoiceAmount + feeReserve
|
||||
const balances = proofsStore.getMintBalancesWithEnoughBalance(totalRequired, unitRef.current)
|
||||
|
||||
|
||||
if (balances.length === 0) {
|
||||
setInfo(
|
||||
translate('transferScreen_insufficientFunds', {
|
||||
@@ -759,16 +532,17 @@ export const NfcPayScreen = observer(function NfcPayScreen({ route }: Props) {
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
const selectedBalance = balances[0]
|
||||
setMintBalanceToSendFrom(selectedBalance)
|
||||
|
||||
|
||||
setInfo('Creating Lightning payment quote...')
|
||||
const quote = await walletStore.createLightningMeltQuote(
|
||||
selectedBalance.mintUrl,
|
||||
unitRef.current,
|
||||
encodedInvoice
|
||||
)
|
||||
|
||||
|
||||
// Update UI
|
||||
setEncodedInvoice(encodedInvoice)
|
||||
setInvoice(decoded)
|
||||
@@ -776,19 +550,78 @@ export const NfcPayScreen = observer(function NfcPayScreen({ route }: Props) {
|
||||
setMeltQuote(quote)
|
||||
setAmountToPay(formatDisplayAmount(quote.amount, unitRef.current))
|
||||
if (description) setMemo(description)
|
||||
setIsTransferTaskSentToQueue(true)
|
||||
|
||||
try {
|
||||
setInfo('Paying Lightning invoice...')
|
||||
setIsLoading(true)
|
||||
|
||||
const result = await WalletTask.transferQueueAwaitable(
|
||||
selectedBalance,
|
||||
quote.amount,
|
||||
unitRef.current,
|
||||
quote,
|
||||
description ?? '',
|
||||
expiresAt,
|
||||
encodedInvoice
|
||||
)
|
||||
|
||||
WalletTask.transferQueue(
|
||||
selectedBalance,
|
||||
quote.amount,
|
||||
unitRef.current,
|
||||
quote,
|
||||
description ?? '',
|
||||
expiresAt,
|
||||
encodedInvoice,
|
||||
undefined,
|
||||
undefined
|
||||
)
|
||||
const { transaction, message, finalFee } = result
|
||||
|
||||
if (!transaction && error) {
|
||||
setTransactionStatus(TransactionStatus.ERROR)
|
||||
|
||||
setResultModalInfo({
|
||||
status: TransactionStatus.ERROR,
|
||||
title: translate('payCommon_failed'),
|
||||
message: error.message || 'Lightning payment failed',
|
||||
})
|
||||
|
||||
toggleResultModal()
|
||||
return;
|
||||
}
|
||||
|
||||
// ——— Transaction exists ———
|
||||
if (transaction) {
|
||||
const { status } = transaction;
|
||||
setTransactionStatus(status);
|
||||
setTransaction(transaction);
|
||||
|
||||
if (error) {
|
||||
// Pending but timed out / failed
|
||||
if (status === TransactionStatus.PENDING) {
|
||||
setResultModalInfo({
|
||||
status,
|
||||
message,
|
||||
})
|
||||
} else {
|
||||
setResultModalInfo({
|
||||
status,
|
||||
title: error.params?.message ? error.message : translate('payCommon_failed'),
|
||||
message: error.params?.message || error.message || 'Lightning payment failed',
|
||||
})
|
||||
}
|
||||
} else {
|
||||
// Success or settled pending
|
||||
setResultModalInfo({
|
||||
status,
|
||||
message: message || 'Lightning payment successful!',
|
||||
title: status === TransactionStatus.COMPLETED ? 'Payment sent!' : undefined,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Fallback (shouldn't happen)
|
||||
setResultModalInfo({
|
||||
status: TransactionStatus.ERROR,
|
||||
title: 'Unknown error',
|
||||
message: 'No transaction data received',
|
||||
})
|
||||
}
|
||||
|
||||
toggleResultModal()
|
||||
} finally {
|
||||
setIsLoading(false)
|
||||
setInfo('')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -859,21 +692,6 @@ export const NfcPayScreen = observer(function NfcPayScreen({ route }: Props) {
|
||||
})
|
||||
}
|
||||
|
||||
const writeTokenToNfcTag = async (token: string) => {
|
||||
try {
|
||||
// Re-request tech to write back
|
||||
await NfcManager.requestTechnology(NfcTech.Ndef)
|
||||
|
||||
const textRecord = Ndef.textRecord('en', token)
|
||||
const bytes = Ndef.encodeMessage([textRecord])
|
||||
|
||||
await NfcManager.ndefHandler.writeNdefMessage(bytes)
|
||||
} catch (e: any) {
|
||||
throw new AppError(Err.NFC_ERROR, 'Failed to send token', e.message)
|
||||
} finally {
|
||||
NfcManager.cancelTechnologyRequest().catch(() => {})
|
||||
}
|
||||
}
|
||||
|
||||
const handleError = (e: AppError) => {
|
||||
setError(e)
|
||||
@@ -902,7 +720,6 @@ export const NfcPayScreen = observer(function NfcPayScreen({ route }: Props) {
|
||||
setInfo('')
|
||||
setError(undefined)
|
||||
setIsLoading(false)
|
||||
setIsTransferTaskSentToQueue(false)
|
||||
setIsResultModalVisible(false)
|
||||
setResultModalInfo(undefined)
|
||||
}
|
||||
@@ -969,7 +786,7 @@ export const NfcPayScreen = observer(function NfcPayScreen({ route }: Props) {
|
||||
</View>
|
||||
</View>
|
||||
|
||||
{info && !isProcessing && <InfoModal message={info} />}
|
||||
{info && <InfoModal message={info} />}
|
||||
{error && <ErrorModal error={error} />}
|
||||
<BottomModal
|
||||
isVisible={isResultModalVisible}
|
||||
|
||||
@@ -76,7 +76,8 @@ export const WalletScreen = observer(function WalletScreen({ route }: Props) {
|
||||
} = useStores()
|
||||
|
||||
const appState = useRef(AppState.currentState)
|
||||
const lastMintCheckRef = useRef(0)
|
||||
const lastMintCheckRef = useRef<number>(0)
|
||||
const lastBackgroundTimestampRef = useRef<number>(0)
|
||||
const isInternetReachable = useIsInternetReachable()
|
||||
const groupedMints: MintsByUnit[] = mintsStore.groupedByUnit
|
||||
|
||||
@@ -312,22 +313,46 @@ export const WalletScreen = observer(function WalletScreen({ route }: Props) {
|
||||
}, [isInternetReachable, userSettingsStore.exchangeCurrency])
|
||||
|
||||
|
||||
useFocusEffect(performChecks)
|
||||
useFocusEffect(() => {
|
||||
log.trace('[useFocusEffect] WalletScreen')
|
||||
performChecks()
|
||||
})
|
||||
|
||||
|
||||
|
||||
useEffect(() => {
|
||||
const handleAppStateChange = (nextAppState: AppStateStatus) => {
|
||||
/* const handleAppStateChange = (nextAppState: AppStateStatus) => {
|
||||
|
||||
if (appState.current.match(/inactive|background/) && nextAppState === 'active') {
|
||||
log.trace('[handleAppStateChange] WalletScreen active again')
|
||||
performChecks()
|
||||
NostrClient.reconnectToRelays().catch(e => false)
|
||||
}
|
||||
appState.current = nextAppState
|
||||
};
|
||||
}*/
|
||||
|
||||
const subscription = AppState.addEventListener('change', handleAppStateChange)
|
||||
const subscription = AppState.addEventListener('change', (nextState) => {
|
||||
if (nextState === 'background') {
|
||||
lastBackgroundTimestampRef.current = Date.now()
|
||||
return
|
||||
}
|
||||
|
||||
if (nextState === 'active') {
|
||||
const timeInBackground = Date.now() - lastBackgroundTimestampRef.current
|
||||
if (timeInBackground < 1000) { // (usually <1s for NFC)
|
||||
log.trace('[handleAppStateChange] Ignored too short background event (could be NFC tap)', { timeInBackground })
|
||||
return // Ignore – this was an NFC flicker
|
||||
}
|
||||
|
||||
// Real foreground – run your normal logic (e.g., unlock check, etc.)
|
||||
log.trace('[handleAppStateChange] WalletScreen active again')
|
||||
performChecks()
|
||||
NostrClient.reconnectToRelays().catch(e => false)
|
||||
}
|
||||
})
|
||||
|
||||
return () => subscription.remove()
|
||||
|
||||
return () => {
|
||||
subscription.remove() // Ensure cleanup to avoid multiple listeners
|
||||
}
|
||||
}, [performChecks])
|
||||
|
||||
|
||||
|
||||
@@ -100,7 +100,6 @@ const extractEncodedCashuToken = function (maybeToken: string): string {
|
||||
|
||||
|
||||
const extractEncodedCashuPaymentRequest = function (maybeRequest: string): string {
|
||||
|
||||
log.trace('[extractEncodedCashuPaymentRequest] Extract payment request from', {maybeRequest})
|
||||
|
||||
let encodedRequest: string | undefined = undefined
|
||||
|
||||
@@ -110,7 +110,7 @@ const findAndExtract = function (
|
||||
const maybeCashuPaymentRequest = CashuUtils.findEncodedCashuPaymentRequest(incomingData)
|
||||
|
||||
if(maybeCashuPaymentRequest) {
|
||||
log.trace('Got maybeCashuPaymentRequest', maybeCashuPaymentRequest, 'findAndExtract')
|
||||
log.trace('Got maybeCashuPaymentRequest', {maybeCashuPaymentRequest, caller: 'findAndExtract'})
|
||||
|
||||
const encoded = CashuUtils.extractEncodedCashuPaymentRequest(maybeCashuPaymentRequest) // throws
|
||||
|
||||
@@ -123,7 +123,7 @@ const findAndExtract = function (
|
||||
const maybeCashuPaymentRequestPayload = CashuUtils.findEncodedCashuPaymentRequestPayload(incomingData)
|
||||
|
||||
if(maybeCashuPaymentRequestPayload) {
|
||||
log.trace('Got maybeCashuPaymentRequestPayload', maybeCashuPaymentRequestPayload, 'findAndExtract')
|
||||
log.trace('Got maybeCashuPaymentRequestPayload', {maybeCashuPaymentRequestPayload, caller: 'findAndExtract'})
|
||||
|
||||
const encoded = incomingData // to be aligned with other cases we pass encoded (stringified) JSON payload
|
||||
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
import { Platform } from 'react-native'
|
||||
import NfcManager, { NfcTech, Ndef, NfcEvents, TagEvent } from 'react-native-nfc-manager'
|
||||
import { log } from './logService'
|
||||
import AppError, { Err } from '../utils/AppError'
|
||||
|
||||
const NFC_STALE_DELAY_MS = 120
|
||||
|
||||
const init = async function () {
|
||||
const supported = await NfcManager.isSupported()
|
||||
if (supported) {
|
||||
await NfcManager.start()
|
||||
|
||||
}
|
||||
return supported
|
||||
}
|
||||
|
||||
const isEnabled = function () {
|
||||
return NfcManager.isEnabled()
|
||||
}
|
||||
|
||||
const goToNfcSetting = function () {
|
||||
return NfcManager.goToNfcSetting()
|
||||
}
|
||||
|
||||
/**
|
||||
* Ensures a fresh NFC connection by cancelling any previous session
|
||||
* and adding a tiny delay on Android
|
||||
*/
|
||||
const withFreshNfcConnection = async <T>(action: () => Promise<T>): Promise<T> => {
|
||||
// Cancel any previous session
|
||||
await NfcManager.cancelTechnologyRequest().catch(() => {})
|
||||
log.trace('[withFreshNfcConnection] Pre-cancel completed')
|
||||
|
||||
if (Platform.OS === 'android') {
|
||||
await new Promise(r => setTimeout(r, NFC_STALE_DELAY_MS))
|
||||
}
|
||||
|
||||
try {
|
||||
return await action() // Only requestTechnology + getTag / writeNdef
|
||||
} finally {
|
||||
// This now runs IMMEDIATELY after read/write
|
||||
await NfcManager.cancelTechnologyRequest().catch(() => {})
|
||||
log.trace('[withFreshNfcConnection] Post-cancel completed')
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const readNdefTag = async () => {
|
||||
return await withFreshNfcConnection(async () => {
|
||||
log.trace('[readNdefTag] requesting technology')
|
||||
await NfcManager.requestTechnology(NfcTech.Ndef)
|
||||
log.trace('[readNdefTag] requestTechnology completed')
|
||||
const tag = await NfcManager.getTag()
|
||||
|
||||
return tag
|
||||
})
|
||||
}
|
||||
|
||||
const writeNdefMessage = async (text: string) => {
|
||||
return await withFreshNfcConnection(async () => {
|
||||
await NfcManager.requestTechnology(NfcTech.Ndef)
|
||||
log.trace('[writeNdefMessage] requestTechnology completed')
|
||||
const bytes = Ndef.encodeMessage([Ndef.textRecord('en', text)])
|
||||
await NfcManager.ndefHandler.writeNdefMessage(bytes)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
export const NfcService = {
|
||||
init,
|
||||
isEnabled,
|
||||
goToNfcSetting,
|
||||
readNdefTag,
|
||||
writeNdefMessage,
|
||||
}
|
||||
@@ -22,31 +22,47 @@ const getSyncQueue = function () {
|
||||
return _queue as TaskQueue
|
||||
}
|
||||
|
||||
const addTask = function (taskId: TaskId, task: Promise<any> | any) {
|
||||
const addTask = function <T>(
|
||||
taskId: TaskId,
|
||||
task: Promise<T> | (() => Promise<T>) | any
|
||||
): Promise<T> {
|
||||
const queue = getSyncQueue()
|
||||
|
||||
log.info(`Adding new task ${taskId} to the queue`)
|
||||
|
||||
queue
|
||||
.addTask(
|
||||
const promise = queue.addTask(
|
||||
task,
|
||||
taskId, _handleTaskStatusChange)
|
||||
.then((result: WalletTaskResult | TransactionTaskResult) => _handleTaskResult(taskId, result))
|
||||
|
||||
taskId,
|
||||
_handleTaskStatusChange
|
||||
) as Promise<T>
|
||||
|
||||
// Side effect: handle result when it resolves (fire-and-forget)
|
||||
promise.then((result: any) => {
|
||||
_handleTaskResult(taskId, result as WalletTaskResult | TransactionTaskResult)
|
||||
}).catch(() => {}) // Optional: ignore errors here if handled elsewhere
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
|
||||
const addPrioritizedTask = function (taskId: TaskId, task: Promise<any> | any) {
|
||||
const addPrioritizedTask = function <T>(
|
||||
taskId: TaskId,
|
||||
task: Promise<T> | (() => Promise<T>) | any
|
||||
): Promise<T> {
|
||||
const queue = getSyncQueue()
|
||||
|
||||
log.info(`Adding new high priority task ${taskId} to the queue`)
|
||||
|
||||
queue
|
||||
.addPrioritizedTask(
|
||||
const promise = queue.addPrioritizedTask(
|
||||
task,
|
||||
taskId, _handleTaskStatusChange)
|
||||
.then((result: WalletTaskResult | TransactionTaskResult) => _handleTaskResult(taskId, result))
|
||||
|
||||
taskId,
|
||||
_handleTaskStatusChange
|
||||
) as Promise<T>
|
||||
|
||||
promise.then((result: any) => {
|
||||
_handleTaskResult(taskId, result as WalletTaskResult | TransactionTaskResult)
|
||||
}).catch(() => {})
|
||||
|
||||
return promise
|
||||
}
|
||||
|
||||
// retrieve result of wallet transaction by listening to ev_taskFuncion event
|
||||
|
||||
@@ -3,6 +3,7 @@ import {getSnapshot} from 'mobx-state-tree'
|
||||
import { GiftWrap, EncryptedDirectMessage } from 'nostr-tools/kinds'
|
||||
import {log} from './logService'
|
||||
import {Proof} from '../models/Proof'
|
||||
import EventEmitter from '../utils/eventEmitter'
|
||||
import {
|
||||
Transaction,
|
||||
TransactionData,
|
||||
@@ -67,6 +68,11 @@ type WalletTaskService = {
|
||||
isPending: boolean
|
||||
}
|
||||
) => Promise<void>
|
||||
syncStateWithAllMintsQueueAwaitable: (
|
||||
options: {
|
||||
isPending: boolean
|
||||
}
|
||||
) => Promise<SyncStateTaskResult>
|
||||
syncStateWithMintQueue: (
|
||||
options: {
|
||||
proofsToSync: Proof[],
|
||||
@@ -74,6 +80,13 @@ type WalletTaskService = {
|
||||
isPending: boolean
|
||||
}
|
||||
) => Promise<void>
|
||||
syncStateWithMintQueueAwaitable: (
|
||||
options: {
|
||||
proofsToSync: Proof[],
|
||||
mintUrl: string,
|
||||
isPending: boolean
|
||||
}
|
||||
) => Promise<SyncStateTaskResult>
|
||||
syncStateWithMintTask: (
|
||||
options: {
|
||||
proofsToSync: Proof[],
|
||||
@@ -99,21 +112,47 @@ type WalletTaskService = {
|
||||
nwcEvent?: NostrEvent,
|
||||
draftTransactionId?: number
|
||||
) => Promise<void>
|
||||
transferQueueAwaitable: (
|
||||
mintBalanceToTransferFrom: MintBalance,
|
||||
amountToTransfer: number,
|
||||
unit: MintUnit,
|
||||
meltQuote: MeltQuoteResponse,
|
||||
memo: string,
|
||||
invoiceExpiry: Date,
|
||||
encodedInvoice: string,
|
||||
nwcEvent?: NostrEvent,
|
||||
draftTransactionId?: number
|
||||
) => Promise<TransactionTaskResult>
|
||||
receiveQueue: (
|
||||
token: Token,
|
||||
amountToReceive: number,
|
||||
memo: string,
|
||||
encodedToken: string,
|
||||
) => Promise<void>
|
||||
receiveQueueAwaitable: (
|
||||
token: Token,
|
||||
amountToReceive: number,
|
||||
memo: string,
|
||||
encodedToken: string,
|
||||
) => Promise<TransactionTaskResult>
|
||||
receiveOfflinePrepareQueue: (
|
||||
token: Token,
|
||||
amountToReceive: number,
|
||||
memo: string,
|
||||
encodedToken: string,
|
||||
) => Promise<void>
|
||||
receiveOfflinePrepareQueueAwaitable: (
|
||||
token: Token,
|
||||
amountToReceive: number,
|
||||
memo: string,
|
||||
encodedToken: string,
|
||||
) => Promise<TransactionTaskResult>
|
||||
receiveOfflineCompleteQueue: (
|
||||
transactionId: number
|
||||
) => Promise<void>
|
||||
receiveOfflineCompleteQueueAwaitable: (
|
||||
transactionId: number
|
||||
) => Promise<TransactionTaskResult>
|
||||
sendQueue: (
|
||||
mintBalanceToSendFrom: MintBalance,
|
||||
amountToSend: number,
|
||||
@@ -123,6 +162,15 @@ type WalletTaskService = {
|
||||
p2pk?: { pubkey: string; locktime?: number; refundKeys?: Array<string> },
|
||||
draftTransactionId?: number
|
||||
) => Promise<void>
|
||||
sendQueueAwaitable: (
|
||||
mintBalanceToSendFrom: MintBalance,
|
||||
amountToSend: number,
|
||||
unit: MintUnit,
|
||||
memo: string,
|
||||
selectedProofs: Proof[],
|
||||
p2pk?: { pubkey: string; locktime?: number; refundKeys?: Array<string> },
|
||||
draftTransactionId?: number
|
||||
) => Promise<TransactionTaskResult>
|
||||
swapAllQueue: () => Promise<void>
|
||||
topupQueue: (
|
||||
mintBalanceToTopup: MintBalance,
|
||||
@@ -132,15 +180,32 @@ type WalletTaskService = {
|
||||
contactToSendTo?: Contact,
|
||||
nwcEvent?: NostrEvent
|
||||
) => Promise<void>
|
||||
topupQueueAwaitable: (
|
||||
mintBalanceToTopup: MintBalance,
|
||||
amountToTopup: number,
|
||||
unit: MintUnit,
|
||||
memo: string,
|
||||
contactToSendTo?: Contact,
|
||||
nwcEvent?: NostrEvent
|
||||
) => Promise<TransactionTaskResult>
|
||||
cashuPaymentRequestQueue: (
|
||||
mintBalanceToReceiveTo: MintBalance,
|
||||
amountToRequest: number,
|
||||
unit: MintUnit,
|
||||
memo: string,
|
||||
) => Promise<void>
|
||||
cashuPaymentRequestQueueAwaitable: (
|
||||
mintBalanceToReceiveTo: MintBalance,
|
||||
amountToRequest: number,
|
||||
unit: MintUnit,
|
||||
memo: string,
|
||||
) => Promise<TransactionTaskResult>
|
||||
revertQueue: (
|
||||
transaction: Transaction
|
||||
) => Promise<void>
|
||||
revertQueueAwaitable: (
|
||||
transaction: Transaction
|
||||
) => Promise<TransactionTaskResult>
|
||||
testQueue: () => Promise<void>
|
||||
recoverMintQuote: (params: {
|
||||
mintUrl: string,
|
||||
@@ -234,6 +299,84 @@ const transferQueue = async function (
|
||||
}
|
||||
|
||||
|
||||
const transferQueueAwaitable = function (
|
||||
mintBalanceToTransferFrom: MintBalance,
|
||||
amountToTransfer: number,
|
||||
unit: MintUnit,
|
||||
meltQuote: MeltQuoteResponse,
|
||||
memo: string,
|
||||
invoiceExpiry: Date,
|
||||
encodedInvoice: string,
|
||||
nwcEvent?: NostrEvent,
|
||||
draftTransactionId?: number
|
||||
): Promise<TransactionTaskResult> {
|
||||
const now = Date.now()
|
||||
const taskId = `transferTask-${now}`
|
||||
|
||||
return new Promise<TransactionTaskResult>((resolve, reject) => {
|
||||
let resolved = false
|
||||
|
||||
const resolveOnce = (result: TransactionTaskResult) => {
|
||||
if (resolved) return
|
||||
resolved = true
|
||||
resolve(result)
|
||||
}
|
||||
|
||||
const rejectOnce = (error: any) => {
|
||||
if (resolved) return
|
||||
resolved = true
|
||||
reject(error)
|
||||
}
|
||||
|
||||
const eventName = `ev_transferTask_result`
|
||||
const handler = (result: TransactionTaskResult) => {
|
||||
EventEmitter.off(eventName, handler)
|
||||
resolveOnce(result)
|
||||
}
|
||||
|
||||
EventEmitter.on(eventName, handler)
|
||||
|
||||
SyncQueue.addPrioritizedTask<TransactionTaskResult>(
|
||||
taskId,
|
||||
async () => {
|
||||
try {
|
||||
return await transferTask(
|
||||
mintBalanceToTransferFrom,
|
||||
amountToTransfer,
|
||||
unit,
|
||||
meltQuote,
|
||||
memo,
|
||||
invoiceExpiry,
|
||||
encodedInvoice,
|
||||
nwcEvent,
|
||||
draftTransactionId
|
||||
)
|
||||
} catch (error) {
|
||||
rejectOnce(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
)
|
||||
.then((result) => {
|
||||
if (!resolved) {
|
||||
resolveOnce(result)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
rejectOnce(error)
|
||||
})
|
||||
|
||||
// Safety timeout
|
||||
setTimeout(() => {
|
||||
if (!resolved) {
|
||||
EventEmitter.off(eventName, handler)
|
||||
rejectOnce(new AppError(Err.TIMEOUT_ERROR, 'transferQueue timed out'))
|
||||
}
|
||||
}, 15000)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const receiveQueue = async function (
|
||||
token: Token,
|
||||
amountToReceive: number,
|
||||
@@ -271,6 +414,79 @@ const receiveQueue = async function (
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
const receiveQueueAwaitable = function (
|
||||
token: Token,
|
||||
amountToReceive: number,
|
||||
memo: string,
|
||||
encodedToken: string,
|
||||
): Promise<TransactionTaskResult> {
|
||||
const now = Date.now()
|
||||
const proofsCount = token.proofs.length
|
||||
|
||||
const taskId = proofsCount > MAX_SWAP_INPUT_SIZE
|
||||
? `receiveBatchTask-${now}`
|
||||
: `receiveTask-${now}`
|
||||
|
||||
const eventName = proofsCount > MAX_SWAP_INPUT_SIZE
|
||||
? `ev_receiveBatchTask_result`
|
||||
: `ev_receiveTask_result`
|
||||
|
||||
return new Promise<TransactionTaskResult>((resolve, reject) => {
|
||||
let resolved = false
|
||||
|
||||
const resolveOnce = (result: TransactionTaskResult) => {
|
||||
if (resolved) return
|
||||
resolved = true
|
||||
resolve(result)
|
||||
}
|
||||
|
||||
const rejectOnce = (error: any) => {
|
||||
if (resolved) return
|
||||
resolved = true
|
||||
reject(error)
|
||||
}
|
||||
|
||||
const handler = (result: TransactionTaskResult) => {
|
||||
EventEmitter.off(eventName, handler)
|
||||
resolveOnce(result)
|
||||
}
|
||||
|
||||
EventEmitter.on(eventName, handler)
|
||||
|
||||
const taskPromise = proofsCount > MAX_SWAP_INPUT_SIZE
|
||||
? receiveBatchTask(token, amountToReceive, memo, encodedToken)
|
||||
: receiveTask(token, amountToReceive, memo, encodedToken)
|
||||
|
||||
SyncQueue.addPrioritizedTask<TransactionTaskResult>(
|
||||
taskId,
|
||||
async () => {
|
||||
try {
|
||||
return await taskPromise
|
||||
} catch (error) {
|
||||
rejectOnce(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
)
|
||||
.then((result) => {
|
||||
if (!resolved) {
|
||||
resolveOnce(result)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
rejectOnce(error)
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
if (!resolved) {
|
||||
EventEmitter.off(eventName, handler)
|
||||
rejectOnce(new AppError(Err.TIMEOUT_ERROR, 'receiveQueue timed out'))
|
||||
}
|
||||
}, 15000)
|
||||
})
|
||||
}
|
||||
|
||||
/*
|
||||
* Receive big tokens in batches to keep mint load reasonable.
|
||||
*
|
||||
@@ -364,6 +580,74 @@ const receiveOfflinePrepareQueue = async function (
|
||||
}
|
||||
|
||||
|
||||
const receiveOfflinePrepareQueueAwaitable = function (
|
||||
token: Token,
|
||||
amountToReceive: number,
|
||||
memo: string,
|
||||
encodedToken: string,
|
||||
): Promise<TransactionTaskResult> {
|
||||
const now = Date.now()
|
||||
const taskId = `receiveOfflinePrepareTask-${now}`
|
||||
|
||||
return new Promise<TransactionTaskResult>((resolve, reject) => {
|
||||
let resolved = false
|
||||
|
||||
const resolveOnce = (result: TransactionTaskResult) => {
|
||||
if (resolved) return
|
||||
resolved = true
|
||||
resolve(result)
|
||||
}
|
||||
|
||||
const rejectOnce = (error: any) => {
|
||||
if (resolved) return
|
||||
resolved = true
|
||||
reject(error)
|
||||
}
|
||||
|
||||
const eventName = `ev_receiveOfflinePrepareTask_result`
|
||||
const handler = (result: TransactionTaskResult) => {
|
||||
EventEmitter.off(eventName, handler)
|
||||
resolveOnce(result)
|
||||
}
|
||||
|
||||
EventEmitter.on(eventName, handler)
|
||||
|
||||
SyncQueue.addTask<TransactionTaskResult>(
|
||||
taskId,
|
||||
async () => {
|
||||
try {
|
||||
return await receiveOfflinePrepareTask(
|
||||
token,
|
||||
amountToReceive,
|
||||
memo,
|
||||
encodedToken
|
||||
)
|
||||
} catch (error) {
|
||||
rejectOnce(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
)
|
||||
.then((result) => {
|
||||
if (!resolved) {
|
||||
resolveOnce(result)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
rejectOnce(error)
|
||||
})
|
||||
|
||||
// Timeout after 20s (offline tasks can be slower)
|
||||
setTimeout(() => {
|
||||
if (!resolved) {
|
||||
EventEmitter.off(eventName, handler)
|
||||
rejectOnce(new AppError(Err.TIMEOUT_ERROR, 'Offline receive prepare timed out'))
|
||||
}
|
||||
}, 20000)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const receiveOfflineCompleteQueue = async function (
|
||||
transactionId: number
|
||||
): Promise<void> {
|
||||
@@ -378,6 +662,65 @@ const receiveOfflineCompleteQueue = async function (
|
||||
}
|
||||
|
||||
|
||||
const receiveOfflineCompleteQueueAwaitable = function (
|
||||
transactionId: number
|
||||
): Promise<TransactionTaskResult> {
|
||||
const now = Date.now()
|
||||
const taskId = `receiveOfflineCompleteTask-${now}`
|
||||
|
||||
return new Promise<TransactionTaskResult>((resolve, reject) => {
|
||||
let resolved = false
|
||||
|
||||
const resolveOnce = (result: TransactionTaskResult) => {
|
||||
if (resolved) return
|
||||
resolved = true
|
||||
resolve(result)
|
||||
}
|
||||
|
||||
const rejectOnce = (error: any) => {
|
||||
if (resolved) return
|
||||
resolved = true
|
||||
reject(error)
|
||||
}
|
||||
|
||||
const eventName = `ev_receiveOfflineCompleteTask_result`
|
||||
const handler = (result: TransactionTaskResult) => {
|
||||
EventEmitter.off(eventName, handler)
|
||||
resolveOnce(result)
|
||||
}
|
||||
|
||||
EventEmitter.on(eventName, handler)
|
||||
|
||||
SyncQueue.addTask<TransactionTaskResult>(
|
||||
taskId,
|
||||
async () => {
|
||||
try {
|
||||
return await receiveOfflineCompleteTask(transactionId)
|
||||
} catch (error) {
|
||||
rejectOnce(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
)
|
||||
.then((result) => {
|
||||
if (!resolved) {
|
||||
resolveOnce(result)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
rejectOnce(error)
|
||||
})
|
||||
|
||||
setTimeout(() => {
|
||||
if (!resolved) {
|
||||
EventEmitter.off(eventName, handler)
|
||||
rejectOnce(new AppError(Err.TIMEOUT_ERROR, 'Offline receive complete timed out'))
|
||||
}
|
||||
}, 20000)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const sendQueue = async function (
|
||||
mintBalanceToSendFrom: MintBalance,
|
||||
amountToSend: number,
|
||||
@@ -404,6 +747,81 @@ const sendQueue = async function (
|
||||
}
|
||||
|
||||
|
||||
const sendQueueAwaitable = function (
|
||||
mintBalanceToSendFrom: MintBalance,
|
||||
amountToSend: number,
|
||||
unit: MintUnit,
|
||||
memo: string,
|
||||
selectedProofs: Proof[],
|
||||
p2pk?: { pubkey: string; locktime?: number; refundKeys?: Array<string> },
|
||||
draftTransactionId?: number
|
||||
): Promise<TransactionTaskResult> {
|
||||
const now = Date.now()
|
||||
const taskId = `sendTask-${now}`
|
||||
|
||||
return new Promise<TransactionTaskResult>((resolve, reject) => {
|
||||
let resolved = false
|
||||
|
||||
const resolveOnce = (result: TransactionTaskResult) => {
|
||||
if (resolved) return
|
||||
resolved = true
|
||||
resolve(result)
|
||||
}
|
||||
|
||||
const rejectOnce = (error: any) => {
|
||||
if (resolved) return
|
||||
resolved = true
|
||||
reject(error)
|
||||
}
|
||||
|
||||
const eventName = `ev_sendTask_result`
|
||||
const handler = (result: TransactionTaskResult) => {
|
||||
EventEmitter.off(eventName, handler)
|
||||
resolveOnce(result)
|
||||
}
|
||||
|
||||
EventEmitter.on(eventName, handler)
|
||||
|
||||
// Explicitly type the generic when calling
|
||||
SyncQueue.addPrioritizedTask<TransactionTaskResult>(
|
||||
taskId,
|
||||
async () => {
|
||||
try {
|
||||
return await sendTask(
|
||||
mintBalanceToSendFrom,
|
||||
amountToSend,
|
||||
unit,
|
||||
memo,
|
||||
selectedProofs,
|
||||
p2pk,
|
||||
draftTransactionId
|
||||
)
|
||||
} catch (error) {
|
||||
rejectOnce(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
)
|
||||
.then((result) => { // Now result is correctly typed as TransactionTaskResult
|
||||
if (!resolved) {
|
||||
resolveOnce(result)
|
||||
}
|
||||
})
|
||||
.catch((error) => {
|
||||
rejectOnce(error)
|
||||
})
|
||||
|
||||
// Timeout safety
|
||||
setTimeout(() => {
|
||||
if (!resolved) {
|
||||
EventEmitter.off(eventName, handler)
|
||||
rejectOnce(new AppError(Err.TIMEOUT_ERROR, 'sendQueue timed out'))
|
||||
}
|
||||
}, 10000)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/*
|
||||
* swapAllTask sends all proofs to pending and swaps them with the mint for standard amount preference
|
||||
* This decreases the total number of proofs held by the wallet. Used to optimize exported backup size.
|
||||
@@ -556,6 +974,73 @@ const topupQueue = async function (
|
||||
}
|
||||
|
||||
|
||||
const topupQueueAwaitable = function (
|
||||
mintBalanceToTopup: MintBalance,
|
||||
amountToTopup: number,
|
||||
unit: MintUnit,
|
||||
memo: string,
|
||||
contactToSendTo?: Contact,
|
||||
nwcEvent?: NostrEvent
|
||||
): Promise<TransactionTaskResult> {
|
||||
const now = Date.now()
|
||||
const taskId = `topupTask-${now}`
|
||||
|
||||
return new Promise<TransactionTaskResult>((resolve, reject) => {
|
||||
let resolved = false
|
||||
|
||||
const resolveOnce = (result: TransactionTaskResult) => {
|
||||
if (resolved) return
|
||||
resolved = true
|
||||
resolve(result)
|
||||
}
|
||||
|
||||
const rejectOnce = (error: any) => {
|
||||
if (resolved) return
|
||||
resolved = true
|
||||
reject(error)
|
||||
}
|
||||
|
||||
const eventName = `ev_topupTask_result`
|
||||
const handler = (result: TransactionTaskResult) => {
|
||||
EventEmitter.off(eventName, handler)
|
||||
resolveOnce(result)
|
||||
}
|
||||
|
||||
EventEmitter.on(eventName, handler)
|
||||
|
||||
SyncQueue.addPrioritizedTask<TransactionTaskResult>(
|
||||
taskId,
|
||||
async () => {
|
||||
try {
|
||||
return await topupTask(
|
||||
mintBalanceToTopup,
|
||||
amountToTopup,
|
||||
unit,
|
||||
memo,
|
||||
contactToSendTo,
|
||||
nwcEvent
|
||||
)
|
||||
} catch (error) {
|
||||
rejectOnce(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
)
|
||||
.then((result) => {
|
||||
if (!resolved) resolveOnce(result)
|
||||
})
|
||||
.catch(rejectOnce)
|
||||
|
||||
setTimeout(() => {
|
||||
if (!resolved) {
|
||||
EventEmitter.off(eventName, handler)
|
||||
rejectOnce(new AppError(Err.TIMEOUT_ERROR, 'Topup task timed out'))
|
||||
}
|
||||
}, 15000)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
const cashuPaymentRequestQueue = async function (
|
||||
mintBalanceToReceiveTo: MintBalance,
|
||||
amountToRequest: number,
|
||||
@@ -576,6 +1061,69 @@ const cashuPaymentRequestQueue = async function (
|
||||
}
|
||||
|
||||
|
||||
const cashuPaymentRequestQueueAwaitable = function (
|
||||
mintBalanceToReceiveTo: MintBalance,
|
||||
amountToRequest: number,
|
||||
unit: MintUnit,
|
||||
memo: string,
|
||||
): Promise<TransactionTaskResult> {
|
||||
const now = Date.now()
|
||||
const taskId = `cashuPaymentRequestTask-${now}`
|
||||
|
||||
return new Promise<TransactionTaskResult>((resolve, reject) => {
|
||||
let resolved = false
|
||||
|
||||
const resolveOnce = (result: TransactionTaskResult) => {
|
||||
if (resolved) return
|
||||
resolved = true
|
||||
resolve(result)
|
||||
}
|
||||
|
||||
const rejectOnce = (error: any) => {
|
||||
if (resolved) return
|
||||
resolved = true
|
||||
reject(error)
|
||||
}
|
||||
|
||||
const eventName = `ev_cashuPaymentRequestTask_result`
|
||||
const handler = (result: TransactionTaskResult) => {
|
||||
EventEmitter.off(eventName, handler)
|
||||
resolveOnce(result)
|
||||
}
|
||||
|
||||
EventEmitter.on(eventName, handler)
|
||||
|
||||
SyncQueue.addPrioritizedTask<TransactionTaskResult>(
|
||||
taskId,
|
||||
async () => {
|
||||
try {
|
||||
return await cashuPaymentRequestTask(
|
||||
mintBalanceToReceiveTo,
|
||||
amountToRequest,
|
||||
unit,
|
||||
memo
|
||||
)
|
||||
} catch (error) {
|
||||
rejectOnce(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
)
|
||||
.then((result) => {
|
||||
if (!resolved) resolveOnce(result)
|
||||
})
|
||||
.catch(rejectOnce)
|
||||
|
||||
setTimeout(() => {
|
||||
if (!resolved) {
|
||||
EventEmitter.off(eventName, handler)
|
||||
rejectOnce(new AppError(Err.TIMEOUT_ERROR, 'Cashu payment request timed out'))
|
||||
}
|
||||
}, 10000)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
const revertQueue = async function (
|
||||
transaction: Transaction
|
||||
@@ -589,6 +1137,61 @@ const revertQueue = async function (
|
||||
}
|
||||
|
||||
|
||||
const revertQueueAwaitable = function (
|
||||
transaction: Transaction
|
||||
): Promise<TransactionTaskResult> {
|
||||
const now = Date.now()
|
||||
const taskId = `revertTask-${now}`
|
||||
|
||||
return new Promise<TransactionTaskResult>((resolve, reject) => {
|
||||
let resolved = false
|
||||
|
||||
const resolveOnce = (result: TransactionTaskResult) => {
|
||||
if (resolved) return
|
||||
resolved = true
|
||||
resolve(result)
|
||||
}
|
||||
|
||||
const rejectOnce = (error: any) => {
|
||||
if (resolved) return
|
||||
resolved = true
|
||||
reject(error)
|
||||
}
|
||||
|
||||
const eventName = `ev_revertTask_result`
|
||||
const handler = (result: TransactionTaskResult) => {
|
||||
EventEmitter.off(eventName, handler)
|
||||
resolveOnce(result)
|
||||
}
|
||||
|
||||
EventEmitter.on(eventName, handler)
|
||||
|
||||
SyncQueue.addPrioritizedTask<TransactionTaskResult>(
|
||||
taskId,
|
||||
async () => {
|
||||
try {
|
||||
return await revertTask(transaction)
|
||||
} catch (error) {
|
||||
rejectOnce(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
)
|
||||
.then((result) => {
|
||||
if (!resolved) resolveOnce(result)
|
||||
})
|
||||
.catch(rejectOnce)
|
||||
|
||||
setTimeout(() => {
|
||||
if (!resolved) {
|
||||
EventEmitter.off(eventName, handler)
|
||||
rejectOnce(new AppError(Err.TIMEOUT_ERROR, 'Revert task timed out'))
|
||||
}
|
||||
}, 15000)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
const syncStateWithAllMintsQueue = async function (options: {
|
||||
isPending: boolean
|
||||
@@ -603,6 +1206,63 @@ const syncStateWithAllMintsQueue = async function (options: {
|
||||
|
||||
|
||||
|
||||
const syncStateWithAllMintsQueueAwaitable = function (
|
||||
options: { isPending: boolean }
|
||||
): Promise<SyncStateTaskResult> {
|
||||
const { isPending } = options
|
||||
const now = Date.now()
|
||||
const taskId = `syncSpendableStateTask-${now}`
|
||||
|
||||
return new Promise<SyncStateTaskResult>((resolve, reject) => {
|
||||
let resolved = false
|
||||
|
||||
const resolveOnce = (result: SyncStateTaskResult) => {
|
||||
if (resolved) return
|
||||
resolved = true
|
||||
resolve(result)
|
||||
}
|
||||
|
||||
const rejectOnce = (error: any) => {
|
||||
if (resolved) return
|
||||
resolved = true
|
||||
reject(error)
|
||||
}
|
||||
|
||||
const eventName = `ev_syncSpendableStateTask_result`
|
||||
const handler = (result: SyncStateTaskResult) => {
|
||||
EventEmitter.off(eventName, handler)
|
||||
resolveOnce(result)
|
||||
}
|
||||
|
||||
EventEmitter.on(eventName, handler)
|
||||
|
||||
SyncQueue.addPrioritizedTask<SyncStateTaskResult>(
|
||||
taskId,
|
||||
async () => {
|
||||
try {
|
||||
return await syncStateWithAllMintsTask({ isPending })
|
||||
} catch (error) {
|
||||
rejectOnce(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
)
|
||||
.then((result) => {
|
||||
if (!resolved) resolveOnce(result)
|
||||
})
|
||||
.catch(rejectOnce)
|
||||
|
||||
setTimeout(() => {
|
||||
if (!resolved) {
|
||||
EventEmitter.off(eventName, handler)
|
||||
rejectOnce(new AppError(Err.TIMEOUT_ERROR, 'Sync all mints state timed out'))
|
||||
}
|
||||
}, 30000) // Longer timeout — syncing all mints can take time
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
const syncStateWithAllMintsTask = async function (options: {
|
||||
isPending: boolean
|
||||
}): Promise<SyncStateTaskResult> {
|
||||
@@ -714,7 +1374,76 @@ const syncStateWithMintQueue = async function (
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
|
||||
const syncStateWithMintQueueAwaitable = function (
|
||||
options: {
|
||||
proofsToSync: Proof[]
|
||||
mintUrl: string
|
||||
isPending: boolean
|
||||
}
|
||||
): Promise<SyncStateTaskResult> {
|
||||
const { mintUrl, isPending, proofsToSync } = options
|
||||
log.trace('[syncStateWithMintQueueAwaitable] start', {
|
||||
mintUrl,
|
||||
isPending,
|
||||
proofsToSyncCount: proofsToSync.length
|
||||
})
|
||||
|
||||
const now = Date.now()
|
||||
const taskId = `syncStateWithMintTask-${now}`
|
||||
|
||||
return new Promise<SyncStateTaskResult>((resolve, reject) => {
|
||||
let resolved = false
|
||||
|
||||
const resolveOnce = (result: SyncStateTaskResult) => {
|
||||
if (resolved) return
|
||||
resolved = true
|
||||
resolve(result)
|
||||
}
|
||||
|
||||
const rejectOnce = (error: any) => {
|
||||
if (resolved) return
|
||||
resolved = true
|
||||
reject(error)
|
||||
}
|
||||
|
||||
const eventName = `ev_syncStateWithMintTask_result`
|
||||
const handler = (result: SyncStateTaskResult) => {
|
||||
EventEmitter.off(eventName, handler)
|
||||
resolveOnce(result)
|
||||
}
|
||||
|
||||
EventEmitter.on(eventName, handler)
|
||||
|
||||
SyncQueue.addTask<SyncStateTaskResult>(
|
||||
taskId,
|
||||
async () => {
|
||||
try {
|
||||
return await syncStateWithMintTask({
|
||||
proofsToSync,
|
||||
mintUrl,
|
||||
isPending
|
||||
})
|
||||
} catch (error) {
|
||||
rejectOnce(error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
)
|
||||
.then((result) => {
|
||||
if (!resolved) resolveOnce(result)
|
||||
})
|
||||
.catch(rejectOnce)
|
||||
|
||||
setTimeout(() => {
|
||||
if (!resolved) {
|
||||
EventEmitter.off(eventName, handler)
|
||||
rejectOnce(new AppError(Err.TIMEOUT_ERROR, 'Sync mint state timed out'))
|
||||
}
|
||||
}, 20000)
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Sync wallet proof state with mint reality (SPENT / PENDING / UNSPENT)
|
||||
@@ -2499,22 +3228,32 @@ const testQueue = async () => {
|
||||
|
||||
export const WalletTask: WalletTaskService = {
|
||||
syncStateWithAllMintsQueue,
|
||||
syncStateWithAllMintsQueueAwaitable,
|
||||
syncStateWithMintQueue,
|
||||
syncStateWithMintQueueAwaitable,
|
||||
syncStateWithMintTask,
|
||||
handleInFlightQueue,
|
||||
handlePendingQueue,
|
||||
handleClaimQueue,
|
||||
handleNwcRequestQueue,
|
||||
receiveEventsFromRelaysQueue,
|
||||
receiveQueue,
|
||||
receiveQueue,
|
||||
receiveQueueAwaitable,
|
||||
receiveOfflinePrepareQueue,
|
||||
receiveOfflineCompleteQueue,
|
||||
receiveOfflinePrepareQueueAwaitable,
|
||||
receiveOfflineCompleteQueue,
|
||||
receiveOfflineCompleteQueueAwaitable,
|
||||
sendQueue,
|
||||
sendQueueAwaitable,
|
||||
swapAllQueue,
|
||||
transferQueue,
|
||||
transferQueue,
|
||||
transferQueueAwaitable,
|
||||
topupQueue,
|
||||
topupQueueAwaitable,
|
||||
cashuPaymentRequestQueue,
|
||||
cashuPaymentRequestQueueAwaitable,
|
||||
revertQueue,
|
||||
revertQueueAwaitable,
|
||||
testQueue,
|
||||
recoverMintQuote,
|
||||
recoverMeltQuoteChange,
|
||||
|
||||
@@ -1894,15 +1894,15 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@cashu/cashu-ts@minibits-cash/cashu-ts#c85d4638c007ac8126bc83a7287a6d848fcc8153":
|
||||
"@cashu/cashu-ts@minibits-cash/cashu-ts#e97b1a4ee9a7ddbb1468302c6996a9d6d3efcb6f":
|
||||
version: 2.4.0
|
||||
resolution: "@cashu/cashu-ts@https://github.com/minibits-cash/cashu-ts.git#commit=c85d4638c007ac8126bc83a7287a6d848fcc8153"
|
||||
resolution: "@cashu/cashu-ts@https://github.com/minibits-cash/cashu-ts.git#commit=e97b1a4ee9a7ddbb1468302c6996a9d6d3efcb6f"
|
||||
dependencies:
|
||||
"@cashu/crypto": ^0.3.4
|
||||
"@noble/curves": ^1.3.0
|
||||
"@noble/hashes": ^1.3.3
|
||||
buffer: ^6.0.3
|
||||
checksum: 9d60865e47509428ee23ed5fec87282ff1fdad1748529010461e9c239d04616b8dde1ed6afbe51bf1c693aeadeaa4e02ede792d3ee2962e99913c1253c061568
|
||||
checksum: 68d46ceb9c60e9cef305aad86534c08b23de434de1efcd217d00c73bdf84704801c3cd700901903256de5a408caf029cea7886e9a303dd423027f08184bc1cb5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@@ -13535,7 +13535,7 @@ __metadata:
|
||||
"@babel/plugin-proposal-export-namespace-from": ^7.18.9
|
||||
"@babel/preset-env": ^7.25.3
|
||||
"@babel/runtime": ^7.25.0
|
||||
"@cashu/cashu-ts": "minibits-cash/cashu-ts#c85d4638c007ac8126bc83a7287a6d848fcc8153"
|
||||
"@cashu/cashu-ts": "minibits-cash/cashu-ts#e97b1a4ee9a7ddbb1468302c6996a9d6d3efcb6f"
|
||||
"@fortawesome/fontawesome-svg-core": ^7.1.0
|
||||
"@fortawesome/free-brands-svg-icons": ^7.1.0
|
||||
"@fortawesome/free-regular-svg-icons": ^7.1.0
|
||||
|
||||
Reference in New Issue
Block a user