1. TimmyIdentityService (artifacts/api-server/src/lib/timmy-identity.ts) - Loads nsec from TIMMY_NOSTR_NSEC env var at boot (bech32 decode) - Generates and warns about ephemeral key if env var absent - sign(EventTemplate) → finalizeEvent() with Timmy's key - encryptDm(recipientPubkeyHex, plaintext) → NIP-04 nip04.encrypt() - Logs npub at server startup 2. ZapService (artifacts/api-server/src/lib/zap.ts) - Constructs NIP-57 zap request event (kind 9734), signs with Timmy's key - Pays via lnbitsService.payInvoice() if bolt11 provided (stub-mode aware) - Logs every outbound event to timmy_nostr_events audit table - maybeZapOnJobComplete() wired in jobs.ts after trustService.recordSuccess() - Config: ZAP_PCT_DEFAULT (default 0 = disabled), ZAP_MIN_SATS (default 10) - Only fires for trusted/elite tier partners when ZAP_PCT_DEFAULT > 0 3. Engagement engine (artifacts/api-server/src/lib/engagement.ts) - Configurable cadence: ENGAGEMENT_INTERVAL_DAYS (default 0 = disabled) - Queries nostrIdentities for trustScore >= 50 AND lastSeen < threshold - Generates personalised DM via agentService.chatReply() - Encrypts as NIP-04 DM (kind 4), signs with Timmy's key - Logs to timmy_nostr_events; publishes to NOSTR_RELAY_URL if set - First run delayed 60s after startup to avoid cold-start noise 4. Vouching endpoint (artifacts/api-server/src/routes/identity.ts) - POST /api/identity/vouch: requires X-Nostr-Token with elite tier - Verifies optional Nostr event signature from voucher - Records relationship in nostr_trust_vouches table - Applies VOUCH_TRUST_BOOST (20 pts) to vouchee's trust score - GET /api/identity/timmy: public endpoint returning npub + zap count 5. DB schema additions (lib/db/src/schema/) - timmy_nostr_events: audit log for all outbound Nostr events - nostr_trust_vouches: voucher/vouchee social graph with boost amount - Tables created in production DB via drizzle-kit push 6. Frontend identity card (the-matrix/) - #timmy-id-card: fixed bottom-right widget with Timmy's npub + zap count - timmy-id.js: initTimmyId() fetches /api/identity/timmy on load - Npub shortened (npub1xxxx...yyyyyy), click-to-copy with feedback - Refreshes every 60s to show live zap count - Wired into main.js on firstInit
63 lines
1.6 KiB
JavaScript
63 lines
1.6 KiB
JavaScript
/**
|
|
* timmy-id.js — Timmy's identity card.
|
|
*
|
|
* Fetches Timmy's Nostr npub from GET /api/identity/timmy and populates
|
|
* the #timmy-id-card widget. Npub is displayed shortened and is copyable.
|
|
* Zap count refreshes every 60 seconds.
|
|
*/
|
|
|
|
const REFRESH_INTERVAL_MS = 60_000;
|
|
|
|
function shortenNpub(npub) {
|
|
if (!npub || npub.length < 16) return npub;
|
|
return npub.slice(0, 10) + '…' + npub.slice(-6);
|
|
}
|
|
|
|
async function fetchTimmyIdentity() {
|
|
try {
|
|
const res = await fetch('/api/identity/timmy');
|
|
if (!res.ok) return;
|
|
return await res.json();
|
|
} catch {
|
|
return null;
|
|
}
|
|
}
|
|
|
|
function renderCard(data) {
|
|
if (!data) return;
|
|
|
|
const $npub = document.getElementById('timmy-npub');
|
|
const $zapCount = document.getElementById('timmy-zap-count');
|
|
|
|
if ($npub && data.npub) {
|
|
$npub.textContent = shortenNpub(data.npub);
|
|
$npub.title = data.npub + '\n(click to copy)';
|
|
|
|
if (!$npub._clickBound) {
|
|
$npub._clickBound = true;
|
|
$npub.addEventListener('click', () => {
|
|
navigator.clipboard.writeText(data.npub).then(() => {
|
|
const orig = $npub.textContent;
|
|
$npub.textContent = 'copied!';
|
|
setTimeout(() => { $npub.textContent = orig; }, 1500);
|
|
}).catch(() => {});
|
|
});
|
|
}
|
|
}
|
|
|
|
if ($zapCount) {
|
|
const n = typeof data.zapCount === 'number' ? data.zapCount : 0;
|
|
$zapCount.textContent = `⚡ ${n} zap${n === 1 ? '' : 's'} sent`;
|
|
}
|
|
}
|
|
|
|
export async function initTimmyId() {
|
|
const data = await fetchTimmyIdentity();
|
|
renderCard(data);
|
|
|
|
setInterval(async () => {
|
|
const refreshed = await fetchTimmyIdentity();
|
|
renderCard(refreshed);
|
|
}, REFRESH_INTERVAL_MS);
|
|
}
|