diff --git a/src/components/CreateNewRocket.svelte b/src/components/CreateNewRocket.svelte index 301b660..3803f09 100644 --- a/src/components/CreateNewRocket.svelte +++ b/src/components/CreateNewRocket.svelte @@ -15,6 +15,7 @@ import type { NDKEventStore } from '@nostr-dev-kit/ndk-svelte'; import { onDestroy } from 'svelte'; import { writable } from 'svelte/store'; + import { BitcoinTipTag } from '@/stores/bitcoin'; let rockets: NDKEventStore | undefined; const rocketsStore = writable([]); @@ -72,6 +73,7 @@ e.tags.push(['ruleset', '334000']); e.tags.push(['ignition', 'this']); e.tags.push(['parent', 'this']); + e.tags.push(BitcoinTipTag()); e.publish().then((x) => { console.log(x); goto(`${base}/rockets/${getRocketURL(e)}`); diff --git a/src/lib/event_helpers/rockets.ts b/src/lib/event_helpers/rockets.ts index d24d247..921b10f 100644 --- a/src/lib/event_helpers/rockets.ts +++ b/src/lib/event_helpers/rockets.ts @@ -2,6 +2,7 @@ import { NDKEvent, type NDKTag } from '@nostr-dev-kit/ndk'; import { MapOfVotes, MeritRequest, Votes } from './merits'; import { getAuthorizedZapper } from '@/helpers'; import validate from 'bitcoin-address-validation'; +import { BitcoinTipTag } from '@/stores/bitcoin'; export class Rocket { Event: NDKEvent; @@ -117,11 +118,12 @@ export class Rocket { event.tags.push(['merit', `${request.Pubkey}:${request.ID}:0:0:${request.Merits}`]); event.tags.push(['proof_full', JSON.stringify(signedProof.rawEvent())]); updateIgnitionAndParentTag(event); + updateBitcionTip(event); } return event; } PendingAMRAuctions(): AMRAuction[] { - let auctions:AMRAuction[] = []; + let auctions: AMRAuction[] = []; for (let t of this.Event.getMatchingTags('amr_auction')) { if (t.length == 2) { let items = t[1].split(':'); @@ -135,7 +137,7 @@ export class Rocket { let ids = items[5].match(/.{1,64}/g); if (ids) { for (let id of ids) { - a.AMRIDs.push(id) + a.AMRIDs.push(id); } } let amrs = this.ApprovedMeritRequests() @@ -161,24 +163,24 @@ export class Rocket { } } } - return auctions + return auctions; } - CanThisAMRBeSold(amr:string):boolean { - let valid = true - let existing = this.ApprovedMeritRequests().get(amr) + CanThisAMRBeSold(amr: string): boolean { + let valid = true; + let existing = this.ApprovedMeritRequests().get(amr); if (!existing) { - valid = false + valid = false; } if (existing && existing.LeadTime > 0) { - valid = false + valid = false; } - let pending = this.PendingAMRAuctions() + let pending = this.PendingAMRAuctions(); for (let p of pending) { if (p.AMRIDs.includes(amr)) { - valid = false + valid = false; } } - return valid + return valid; } UpsertAMRAuction(request: AMRAuction): NDKEvent | undefined { //todo: validate that all items in the request exist and the total amount is correct, from same pubkey @@ -212,6 +214,7 @@ export class Rocket { ]); // event.tags.push(['proof_full', JSON.stringify(request.Event!.rawEvent())]); updateIgnitionAndParentTag(event); + updateBitcionTip(event); } if (invalid) { event = undefined; @@ -235,6 +238,7 @@ export class Rocket { purchases ]); updateIgnitionAndParentTag(event); + updateBitcionTip(event); return event; } UpdateMission(mission: string): NDKEvent { @@ -244,6 +248,7 @@ export class Rocket { event.removeTag('mission'); event.tags.push(['mission', mission]); updateIgnitionAndParentTag(event); + updateBitcionTip(event); return event; } CurrentProducts(): Map { @@ -330,6 +335,25 @@ function updateIgnitionAndParentTag(event: NDKEvent) { event.tags.push(['parent', event.id]); } +function updateBitcionTip(event: NDKEvent) { + let existingBitcoinTip = event.getMatchingTags('bitcoin'); + let existing = []; + for (let t of event.tags) { + existing.push(t); + } + event.tags = []; + for (let t of existing) { + if (t[0] !== 'bitcoin') { + event.tags.push(t); + } + } + if (existingBitcoinTip.length > 1) { + throw new Error('too many bitcoin tip tags!'); + } else { + event.tags.push(BitcoinTipTag()); + } +} + export class RocketAMR { //todo: also add a query for sats tags to find payments for this AMR ID: string; diff --git a/src/lib/helpers.ts b/src/lib/helpers.ts index 795f131..86b7093 100644 --- a/src/lib/helpers.ts +++ b/src/lib/helpers.ts @@ -30,6 +30,10 @@ export function getMission(rocketEvent: NDKEvent): string { return ''; } +export function unixTimeNow() { + return Math.floor(new Date().getTime() / 1000); +} + export function unixToRelativeTime(timestamp: number): string { const currentTime = Date.now(); const secondsAgo = Math.floor((currentTime - timestamp) / 1000); diff --git a/src/lib/stores/bitcoin.ts b/src/lib/stores/bitcoin.ts new file mode 100644 index 0000000..3afc493 --- /dev/null +++ b/src/lib/stores/bitcoin.ts @@ -0,0 +1,32 @@ +import { get, writable } from 'svelte/store'; + +type BitcoinTip = { + height: number; + hash: string; +}; + +let _b: BitcoinTip = { hash: '', height: 0 }; +export let bitcoinTip = writable(_b); + +export function BitcoinTipTag(): string[] { + let tip = get(bitcoinTip); + let bths: string[] = ['bitcoin', '']; + if (tip.hash && tip.height) { + bths = ['bitcoin', tip.height.toString() + ':' + tip.hash]; + } + return bths; +} + +export async function getBitcoinTip() { + const response = await fetch('https://blockstream.info/api/blocks/tip'); + const _json = await response.json(); + if (_json[0]) { + let r: BitcoinTip = { + height: _json[0].height, + hash: _json[0].id + }; + bitcoinTip.set(r); + return r; + } + return null; +} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index afc11a0..080e6ba 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -5,6 +5,8 @@ import { ndk } from '@/ndk'; import type { NDKUser } from '@nostr-dev-kit/ndk'; import { currentUser, prepareUserSession } from '@/stores/session'; + import { unixTimeNow } from '@/helpers'; + import { getBitcoinTip } from '@/stores/bitcoin'; let sessionStarted = false; let connected = false; @@ -24,6 +26,18 @@ }); sessionStarted = true; } + + let lastRequestTime = 0; + + $: { + if (unixTimeNow() > lastRequestTime + 30000) { + getBitcoinTip().then((x) => { + if (x) { + lastRequestTime = unixTimeNow(); + } + }); + } + }