Compare commits

...
2 Commits
3 changed files with 65 additions and 18 deletions
+50 -8
View File
@@ -915,16 +915,56 @@ export function formatTimeAgo(timestamp) {
}
// Store timestamps for real-time updates
const timeAgoElements = new Map(); // element -> timestamp
const timeAgoElements = new Map(); // element -> { timestamp, eventId }
// Track relay publish counts per event ID from broadcastProgress events.
// Keyed by event ID → number of successful relay publishes.
const relayCountByEventId = new Map();
// Listen for broadcast progress events to capture final relay counts.
// The worker emits ndkBroadcastProgress with phase 'done' containing the
// total successful count. We store it so post cards can show "21r - 1h".
if (typeof window !== 'undefined') {
window.addEventListener('ndkBroadcastProgress', (event) => {
const d = event.detail;
if (!d || d.phase !== 'done') return;
const eventId = d.eventId;
if (!eventId) return;
relayCountByEventId.set(eventId, d.successful || 0);
// Update any already-rendered time elements for this event.
timeAgoElements.forEach((info, element) => {
if (info.eventId === eventId && element.isConnected) {
element.textContent = formatTimeAgoWithRelays(info.timestamp, info.eventId);
}
});
});
}
/**
* Format time ago with optional relay count prefix.
* Shows "21r - 1h" when a relay count is available, else just "1h".
* @param {number} timestamp - Unix timestamp in seconds
* @param {string} [eventId] - Optional event ID to look up relay count
* @returns {string}
*/
function formatTimeAgoWithRelays(timestamp, eventId) {
const timeStr = formatTimeAgo(timestamp);
if (eventId && relayCountByEventId.has(eventId)) {
const count = relayCountByEventId.get(eventId);
return `${count}r - ${timeStr}`;
}
return timeStr;
}
/**
* Register an element for time updates
* @param {HTMLElement} element - The element to update
* @param {number} timestamp - Unix timestamp in seconds
* @param {string} [eventId] - Optional event ID for relay count display
*/
export function registerTimeAgo(element, timestamp) {
timeAgoElements.set(element, timestamp);
element.textContent = formatTimeAgo(timestamp);
export function registerTimeAgo(element, timestamp, eventId) {
timeAgoElements.set(element, { timestamp, eventId });
element.textContent = formatTimeAgoWithRelays(timestamp, eventId);
}
/**
@@ -932,9 +972,9 @@ export function registerTimeAgo(element, timestamp) {
* Call this periodically (e.g., every 30 seconds)
*/
export function updateTimeAgos() {
timeAgoElements.forEach((timestamp, element) => {
timeAgoElements.forEach((info, element) => {
if (element.isConnected) {
element.textContent = formatTimeAgo(timestamp);
element.textContent = formatTimeAgoWithRelays(info.timestamp, info.eventId);
} else {
// Clean up detached elements
timeAgoElements.delete(element);
@@ -1085,10 +1125,12 @@ export function renderFooterRow(eventId, eventData, options = {}) {
container.appendChild(nutzapItem);
container.appendChild(midControls);
// Time as the final item in the same row
// Time as the final item in the same row.
// Pass eventId so registerTimeAgo can prepend the relay count
// (e.g., "21r - 1h") when a broadcastProgress 'done' event arrives.
const timeEl = document.createElement('span');
timeEl.className = 'divPostTime';
registerTimeAgo(timeEl, eventData.created_at);
registerTimeAgo(timeEl, eventData.created_at, eventId);
container.appendChild(timeEl);
footerRow.appendChild(container);
+3 -3
View File
@@ -1,5 +1,5 @@
{
"VERSION": "v0.7.68",
"VERSION_NUMBER": "0.7.68",
"BUILD_DATE": "2026-06-30T13:15:02.809Z"
"VERSION": "v0.7.70",
"VERSION_NUMBER": "0.7.70",
"BUILD_DATE": "2026-06-30T13:28:38.113Z"
}
+12 -7
View File
@@ -4616,8 +4616,13 @@ async function handleWalletPayInvoice(requestId, invoice, mint, port) {
const meltQuote = await wallet.createMeltQuote(pr);
const quoteAmount = Number(meltQuote?.amount || 0);
const quoteFeeReserve = Number(meltQuote?.fee_reserve || 0);
// The mint's quoted fee_reserve is an estimate; the actual melt fee can be
// slightly higher, causing "not enough inputs provided for melt" errors.
// Add a safety buffer — any overpayment is returned as change proofs.
const FEE_RESERVE_BUFFER_SATS = 10;
const required = quoteAmount + quoteFeeReserve;
log('mint attempt:quote', { mintUrl, quoteAmount, quoteFeeReserve, required });
const sendAmount = required + FEE_RESERVE_BUFFER_SATS;
log('mint attempt:quote', { mintUrl, quoteAmount, quoteFeeReserve, required, sendAmount });
if (!Number.isFinite(required) || required <= 0) {
log('mint attempt:invalid-quote', { mintUrl, required });
@@ -4625,28 +4630,28 @@ async function handleWalletPayInvoice(requestId, invoice, mint, port) {
continue;
}
if (proofBalance < required) {
log('mint attempt:insufficient', { mintUrl, proofBalance, required });
if (proofBalance < sendAmount) {
log('mint attempt:insufficient', { mintUrl, proofBalance, sendAmount });
attemptErrors.push({ mint: mintUrl, message: `Insufficient funds on mint (${proofBalance} sats)` });
continue;
}
const sendResult = await wallet.send(required, proofs);
const sendResult = await wallet.send(sendAmount, proofs);
const keep = Array.isArray(sendResult?.keep) ? sendResult.keep : [];
const sendProofs = Array.isArray(sendResult?.send) ? sendResult.send : [];
log('mint attempt:send-split', { mintUrl, keepCount: keep.length, sendCount: sendProofs.length });
log('mint attempt:send-split', { mintUrl, keepCount: keep.length, sendCount: sendProofs.length, sendAmount });
const meltResult = await wallet.meltProofs(meltQuote, sendProofs);
const change = Array.isArray(meltResult?.change) ? meltResult.change : [];
upsertMintProofs(mintUrl, [...keep, ...change]);
const changeAmount = getProofTotal(change);
const spentAmount = Math.max(0, required - changeAmount);
const spentAmount = Math.max(0, sendAmount - changeAmount);
paidAmountSats = Number.isFinite(quoteAmount) && quoteAmount > 0
? quoteAmount
: spentAmount;
log('mint attempt:success', { mintUrl, paidAmountSats, changeAmount, preimage: meltResult?.preimage || null });
log('mint attempt:success', { mintUrl, paidAmountSats, changeAmount, spentAmount, preimage: meltResult?.preimage || null });
usedMint = mintUrl;
payResult = meltResult;