diff --git a/src/lib/event_helpers/rockets.ts b/src/lib/event_helpers/rockets.ts index 6db2a57..af2d570 100644 --- a/src/lib/event_helpers/rockets.ts +++ b/src/lib/event_helpers/rockets.ts @@ -2,7 +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, txs } from '@/stores/bitcoin'; +import { BitcoinTipTag, bitcoinTip, txs } from '@/stores/bitcoin'; export class Rocket { UpsertBitcoinAssociation(association: BitcoinAssociation): NDKEvent { @@ -619,6 +619,8 @@ export async function ValidateZapPublisher(rocket: NDKEvent, zap: NDKEvent): Pro }); } +type AMRAuctionStatus = 'PENDING' | 'OPEN' | 'TX DETECTED' | 'SOLD & PENDING RATIFICATION' | 'CHECKING MEMPOOL'; + export class AMRAuction { AMRIDs: string[]; Owner: string | undefined; @@ -630,32 +632,50 @@ export class AMRAuction { Merits: number; Event: NDKEvent; Extra: { rocket: Rocket }; - Status(rocket: Rocket, transactions?: txs): string { - let status = 'PENDING'; + Status( + rocket: Rocket, + bitcoinTip: number, + transactions?: txs + ): AMRAuctionStatus { + let status:AMRAuctionStatus = "PENDING" if (transactions && transactions.Address != this.RxAddress) { throw new Error('invalid address'); } - for (let pending of rocket.PendingAMRAuctions()) { - this.AMRIDs.sort() - pending.AMRIDs.sort() - if ( - pending.Owner == this.Owner && - pending.Merits == this.Merits && - pending.RxAddress == this.RxAddress && - pending.AMRIDs[0] == this.AMRIDs[0] //todo: check whole array - ) { - status = "OPEN" - } - } if (transactions) { + status = 'CHECKING MEMPOOL'; for (let [t, txo] of transactions.From()) { //todo: implement pricing based on block height if (txo.Amount == this.EndPrice && txo.To == this.RxAddress) { - status = "SOLD" + if (txo.Height > 0 && txo.Height < bitcoinTip) { + status = 'SOLD & PENDING RATIFICATION'; + } else { + status = 'TX DETECTED'; + } } } + let found = false; + for (let pending of rocket.PendingAMRAuctions()) { + this.AMRIDs.sort(); + pending.AMRIDs.sort(); + if ( + pending.Owner == this.Owner && + pending.Merits == this.Merits && + pending.RxAddress == this.RxAddress && + pending.AMRIDs[0] == this.AMRIDs[0] //todo: check whole array + ) { + found = true + if (status == "CHECKING MEMPOOL") { + if ( + Math.floor(new Date().getTime() / 1000) < transactions.LastUpdate + 60000 + ) { + status = 'OPEN'; + } + } + } + } + } - return status + return status; } GenerateEvent(): NDKEvent { let e = new NDKEvent(); diff --git a/src/lib/stores/bitcoin.ts b/src/lib/stores/bitcoin.ts index b52ece2..3746612 100644 --- a/src/lib/stores/bitcoin.ts +++ b/src/lib/stores/bitcoin.ts @@ -19,17 +19,45 @@ export function BitcoinTipTag(): string[] { } export async function getBitcoinTip() { + getBitcoinTipBlockstream(); + getBitcoinTipMempool(); +} + +async function getBitcoinTipBlockstream() { try { - 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; - }} catch { + 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 + }; + if (r.hash && r.height) { + bitcoinTip.set(r); + return r; + } + } + } catch { + return null; + } + return null; +} + +async function getBitcoinTipMempool() { + try { + const response = await fetch('https://mempool.space/api/blocks/tip'); + const _json = await response.json(); + if (_json[0]) { + let r: BitcoinTip = { + height: _json[0].height, + hash: _json[0].id + }; + if (r.hash && r.height) { + bitcoinTip.set(r); + return r; + } + } + } catch { return null; } return null; @@ -66,7 +94,7 @@ export async function getBalance(address: string): Promise { }); } -export async function getIncomingTransactions(address: string):Promise { +export async function getIncomingTransactions(address: string): Promise { return new Promise((resolve, reject) => { if (!validate(address)) { reject('invalid address'); @@ -80,7 +108,7 @@ export async function getIncomingTransactions(address: string):Promise { response .json() .then((j) => { - resolve(j) + resolve(j); }) .catch((x) => reject(x)); } @@ -98,40 +126,44 @@ export async function getIncomingTransactions(address: string):Promise { export class txs { Address: string; LastUpdate: number; + LastAttempt: number; Data: JSON; - From():Map { - let possibles = new Map() + From(): Map { + let possibles = new Map(); for (let tx of this.Data) { - let amount = 0 - let height = tx.status.block_height; + let amount = 0; + let height = tx.status.block_height ? tx.status.block_height : 0; let txid = tx.txid; for (let vout of tx.vout) { - let address = vout.scriptpubkey_address + let address = vout.scriptpubkey_address; if (address && address.trim() == this.Address) { - let value = vout.value + let value = vout.value; if (value) { - amount += parseInt(value, 10) + amount += parseInt(value, 10); } } } for (let vin of tx.vin) { - let address = vin.prevout.scriptpubkey_address + let address = vin.prevout.scriptpubkey_address; if (address && validate(address)) { - let t = new txo() - t.Amount = amount - t.Height = height - t.From = address - t.To = this.Address - t.ID = txid - possibles.set(address, t) + let t = new txo(); + t.Amount = amount; + t.Height = height; + t.From = address; + t.To = this.Address; + t.ID = txid; + possibles.set(address, t); + } else { + console.log(156, vin) } } } - return possibles + return possibles; } constructor(address: string) { this.Address = address.trim(); this.LastUpdate = 0; + this.LastAttempt = 0; this.Data = JSON.parse('[]'); } } @@ -142,7 +174,5 @@ export class txo { To: string; Amount: number; Height: number; - constructor() { - - } -} \ No newline at end of file + constructor() {} +} diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index 080e6ba..bd96b84 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,12 +1,12 @@ diff --git a/src/routes/buymerits/+page.svelte b/src/routes/buymerits/+page.svelte index 581f811..51b3e5c 100644 --- a/src/routes/buymerits/+page.svelte +++ b/src/routes/buymerits/+page.svelte @@ -3,14 +3,14 @@ import * as Table from '@/components/ui/table'; import { AMRAuction, Rocket } from '@/event_helpers/rockets'; import { ndk } from '@/ndk'; - import { getIncomingTransactions, txs } from '@/stores/bitcoin'; + import { bitcoinTip, getIncomingTransactions, txs } from '@/stores/bitcoin'; import { currentUser } from '@/stores/session'; import { NDKEvent } from '@nostr-dev-kit/ndk'; import { Avatar } from '@nostr-dev-kit/ndk-svelte-components'; - import validate from 'bitcoin-address-validation'; import { onDestroy } from 'svelte'; import { derived } from 'svelte/store'; import AssociateBitcoinAddress from '../../components/AssociateBitcoinAddress.svelte'; + import Heading from '../../components/Heading.svelte'; import Login from '../../components/Login.svelte'; import MeritAuctions from '../../stateupdaters/MeritAuctions.svelte'; let rocketEvents = $ndk.storeSubscribe([{ kinds: [31108 as number] }], { subId: 'all_rockets' }); @@ -44,9 +44,6 @@ }); let _transactions = new Map(); - - - let transactions = derived(pendingSales, ($pendingSales) => { for (let [r, s] of $pendingSales) { for (let amr of s) { @@ -54,32 +51,59 @@ _transactions.set(amr.RxAddress, new txs(amr.RxAddress)); } let existing = _transactions.get(amr.RxAddress)!; - if (Math.floor(new Date().getTime() / 1000) > existing.LastUpdate + 10000) { - existing.LastUpdate = Math.floor(new Date().getTime() / 1000); + if ( + Math.floor(new Date().getTime() / 1000) > existing.LastAttempt + 10000 + ) { + existing.LastAttempt = Math.floor(new Date().getTime() / 1000); getIncomingTransactions(amr.RxAddress).then((result) => { + if (result) { + existing.LastUpdate = Math.floor(new Date().getTime() / 1000); + } if (result.length > 0) { existing.Data = result; _transactions.set(amr.RxAddress, existing); _transactions = _transactions; } - }); + }).catch(c=>{console.log(c)}); } } } return _transactions; }); - transactions.subscribe((t) => { - //console.log(82, t) + let soldButNotInState = derived( + [pendingSales, transactions, bitcoinTip, currentUser], + ([$pendingSales, $transactions, $bitcoinTip, $currentUser]) => { + if ($currentUser) { + for (let [r, p] of $pendingSales) { + if (r.VotePowerForPubkey($currentUser.pubkey) > 0) { + for (let ps of p) { + if ( + ps.Status(r, $bitcoinTip.height, $transactions.get(ps.RxAddress)) == + 'SOLD & PENDING RATIFICATION' + ) { + } + } + } + } + } + } + ); + + soldButNotInState.subscribe((t) => { + if (t) console.log(t); }); + transactions.subscribe((t) => {}); + let noAssociatedBitcoinAddress = derived( [currentUser, pendingSales], ([$currentUser, $pendingSales]) => { let show = false; if ($currentUser) { - for (let [r, _] of $pendingSales) { - if (!r.BitcoinAssociations().get($currentUser.pubkey)) { + for (let [r, a] of $pendingSales) { + if (a.length > 0 && !r.BitcoinAssociations().get($currentUser.pubkey)) { + console.log($currentUser.pubkey, r.Name()); show = true; } } @@ -94,14 +118,7 @@ {#if $currentUser} {#each $pendingSales as [rocket, amr]} {#if amr.length > 0} -

{ - console.log(rocket.Event.rawEvent(), rocket.PendingAMRAuctions()); - }} - > - ROCKET: {rocket.Name()} -

- + @@ -129,13 +146,19 @@ > {p.Merits} {p.Merits} - {p.Status(rocket, _transactions.get(p.RxAddress))} + {p.Status(rocket, $bitcoinTip.height, $transactions.get(p.RxAddress))} { - console.log(_transactions.get(p.RxAddress)?.From()); + console.log($transactions.get(p.RxAddress)?.From()); }}>{p.RxAddress} - {#if p.Status(rocket, _transactions.get(p.RxAddress)) == "OPEN"}{/if} + {#if p.Status(rocket, $bitcoinTip.height, $transactions.get(p.RxAddress)) == 'OPEN'}{/if} {/each} diff --git a/src/stateupdaters/MeritAuctions.svelte b/src/stateupdaters/MeritAuctions.svelte index d005b01..1877c38 100644 --- a/src/stateupdaters/MeritAuctions.svelte +++ b/src/stateupdaters/MeritAuctions.svelte @@ -57,5 +57,3 @@ //todo: validate and publish rocket updates }); - -