diff --git a/src/components/AddProductToRocket.svelte b/src/components/AddProductToRocket.svelte
index 2662b9c..49b9a4b 100644
--- a/src/components/AddProductToRocket.svelte
+++ b/src/components/AddProductToRocket.svelte
@@ -6,54 +6,21 @@
import { Input } from '$lib/components/ui/input/index.js';
import { Label } from '$lib/components/ui/label/index.js';
import * as Alert from '@/components/ui/alert';
- import { getRocketURL } from '@/helpers';
import { ndk } from '@/ndk';
import { currentUser } from '@/stores/session';
import { NDKEvent } from '@nostr-dev-kit/ndk';
import { Terminal } from 'lucide-svelte';
import Todo from './Todo.svelte';
+ import { Rocket } from '@/event_helpers/rockets';
export let product: NDKEvent;
export let rocket: NDKEvent;
+ let parsedRocket: Rocket = new Rocket(rocket);
+
let price: number = 0;
let max: number = 0;
- function updateIgnitionAndParentTag(rocket: NDKEvent) {
- let existingIgnition = rocket.getMatchingTags('ignition');
- //let existingParent = rocket.getMatchingTags("parent")
- removeIgnitionAndParentTag(rocket);
- if (existingIgnition.length > 1) {
- throw new Error('too many ignition tags!');
- }
- if (existingIgnition.length == 0) {
- rocket.tags.push(['ignition', rocket.id]);
- }
- if (existingIgnition.length == 1) {
- if (existingIgnition[0][1].length == 64) {
- rocket.tags.push(existingIgnition[0]);
- }
- if (existingIgnition[0][1] == 'this') {
- rocket.tags.push(['ignition', rocket.id]);
- }
- }
- rocket.tags.push(['parent', rocket.id]);
-
- }
-
- function removeIgnitionAndParentTag(rocket: NDKEvent) {
- let existing = [];
- for (let t of rocket.tags) {
- existing.push(t);
- }
- rocket.tags = [];
- for (let t of existing) {
- if (t[0] !== 'ignition' && t[0] !== 'parent') {
- rocket.tags.push(t);
- }
- }
- }
-
function publish() {
if (!$ndk.signer) {
throw new Error('no ndk signer found');
@@ -67,16 +34,8 @@
console.log(rocket.author, author);
throw new Error('you are not the creator of this rocket');
}
- rocket.created_at = Math.floor(new Date().getTime() / 1000);
- //todo validate d tag
- rocket.tags.push([
- 'product',
- `${product.id}:${price}:${rocket.created_at}:${max}`,
- 'wss://relay.nostrocket.org',
- JSON.stringify([])
- ]);
- updateIgnitionAndParentTag(rocket)
- rocket.publish().then((x) => {
+ let event = parsedRocket.UpsertProduct(product.id, price, max);
+ event.publish().then((x) => {
console.log(x);
goto(`${base}/products`);
});
diff --git a/src/components/MeritRequests.svelte b/src/components/MeritRequests.svelte
index 9148347..ffee078 100644
--- a/src/components/MeritRequests.svelte
+++ b/src/components/MeritRequests.svelte
@@ -58,7 +58,6 @@
{#each $merits as [id, merit], _ (id)}
{
- console.log(merit.Event.rawEvent());
goto(`${base}/rockets/merits/${merit.ID}`);
}}
class="cursor-pointer bg-accent"
diff --git a/src/components/MeritSummaryCard.svelte b/src/components/MeritSummaryCard.svelte
index c930555..fde2b48 100644
--- a/src/components/MeritSummaryCard.svelte
+++ b/src/components/MeritSummaryCard.svelte
@@ -1,15 +1,13 @@
@@ -116,14 +142,17 @@
Votes
- {#if $votes.size == 0}Waiting for existing {new Rocket(rocket).Name()} Merit holders to vote {/if}
+ {#if $votes.size == 0}Waiting for existing {new Rocket(rocket).Name()} Merit
+ holders to vote
+ {/if}
{#each $votes as [id, vote], _ (id)}
{
console.log(vote.Event.rawEvent());
- goto(`${base}/rockets/merits/${vote.ID}`);
}}
class="cursor-pointer {vote.VoteDirection == 'ratify'
? 'bg-lime-600'
diff --git a/src/lib/event_helpers/merits.ts b/src/lib/event_helpers/merits.ts
index 2e2aeaf..d0424b0 100644
--- a/src/lib/event_helpers/merits.ts
+++ b/src/lib/event_helpers/merits.ts
@@ -31,7 +31,7 @@ export class MeritRequest {
return _solution;
}
IncludedInRocketState(rocket: Rocket): boolean {
- return true;
+ return false;
}
BasicValidation(): boolean {
//todo: make a ValidateAgainstRocket and check that pubkey is in WoT
@@ -85,36 +85,31 @@ export class MeritRequest {
}
export class Vote {
- ID: string;
- Request: string;
- VoteDirection: VoteDirection | undefined;
- Pubkey: string;
- TimeStamp: number;
+ ID: string;
+ Request: string;
+ VoteDirection: VoteDirection | undefined;
+ Pubkey: string;
+ TimeStamp: number;
Event: NDKEvent;
BasicValidation(): boolean {
- let valid = true;
+ let valid = true;
if (
- !(
- this.ID.length == 64 &&
- this.Request.length == 64 &&
- this.VoteDirection &&
- this.TimeStamp
- )
+ !(this.ID.length == 64 && this.Request.length == 64 && this.VoteDirection && this.TimeStamp)
) {
valid = false;
}
return valid;
}
- ValidateAgainstRocket(rocket:Rocket):boolean {
- let valid = true;
- if (!(rocket.VotePowerForPubkey(this.Pubkey) > 0)) {
- valid = false
- }
- return valid
- }
- ValidateAgainstMeritRequest(merit:MeritRequest):boolean {
- return this.Request == merit.ID
- }
+ ValidateAgainstRocket(rocket: Rocket): boolean {
+ let valid = true;
+ if (!(rocket.VotePowerForPubkey(this.Pubkey) > 0)) {
+ valid = false;
+ }
+ return valid;
+ }
+ ValidateAgainstMeritRequest(merit: MeritRequest): boolean {
+ return this.Request == merit.ID;
+ }
RocketTag(): NDKTag | undefined {
let tag: NDKTag | undefined = undefined;
if (this.BasicValidation()) {
@@ -146,22 +141,124 @@ export class Vote {
}
constructor(event: NDKEvent) {
this.Event = event;
- this.ID = event.id;
- this.Pubkey = event.pubkey;
- if (this.Event.created_at) {
+ this.ID = event.id;
+ this.Pubkey = event.pubkey;
+ if (this.Event.created_at) {
this.TimeStamp = this.Event.created_at;
}
- for (let t of this.Event.getMatchingTags("vote")) {
- if (t && t.length == 2 && (t[1] == "blackball" || t[1] == "ratify")) {
- this.VoteDirection = t[1]
- }
- }
- for (let t of this.Event.getMatchingTags("request")) {
- if (t && t.length == 2 && t[1].length == 64) {
- this.Request = t[1]
- }
- }
+ for (let t of this.Event.getMatchingTags('vote')) {
+ if (t && t.length == 2 && (t[1] == 'blackball' || t[1] == 'ratify')) {
+ this.VoteDirection = t[1];
+ }
+ }
+ for (let t of this.Event.getMatchingTags('request')) {
+ if (t && t.length == 2 && t[1].length == 64) {
+ this.Request = t[1];
+ }
+ }
+ if (!this.BasicValidation()) {
+ throw new Error('failed to create vote');
+ }
}
}
-export type VoteDirection = "blackball" | "ratify"
\ No newline at end of file
+export type VoteDirection = 'blackball' | 'ratify';
+
+export class Votes {
+ Votes: Vote[];
+ Request:string;
+ Results(): VoteResults {
+ let ratifiers = new Map();
+ let blackballers = new Map();
+ for (let v of this.Votes) {
+ if ((v.VoteDirection == 'blackball')) {
+ blackballers.set(v.ID, v);
+ }
+ if ((v.VoteDirection == 'ratify')) {
+ ratifiers.set(v.ID, v);
+ }
+ }
+ let results: VoteResults = new VoteResults(
+ new VoteTally(blackballers),
+ new VoteTally(ratifiers)
+ );
+ return results;
+ }
+ constructor(votes: Vote[], request?:string) {
+ this.Votes = []
+ for (let v of votes) {
+ if (!request) {
+ request = v.Request;
+ }
+ if (!this.Request) {
+ this.Request = request
+ }
+ if (v.Request == this.Request) {
+ this.Votes.push(v)
+ }
+ }
+ }
+}
+
+export class VoteResults {
+ blackballers: VoteTally;
+ ratifiers: VoteTally;
+ Result(rocket: Rocket): VoteDirection | undefined {
+ let result: VoteDirection | undefined = undefined;
+ if (this.blackballers.TotalPercent(rocket) < 0.1 && this.ratifiers.TotalPercent(rocket) > 0.5) {
+ result = 'ratify';
+ }
+ if (this.blackballers.TotalPercent(rocket) >= 0.1) {
+ result = 'blackball';
+ }
+ return result;
+ }
+ constructor(blackballers: VoteTally, ratifiers: VoteTally) {
+ this.blackballers = blackballers;
+ this.ratifiers = ratifiers;
+ }
+}
+
+export class VoteTally {
+ Votes: Map;
+ Direction: VoteDirection | undefined;
+ MeritRequest: string | undefined;
+ BasicValidation(): boolean {
+ let valid = true;
+ for (let [_, v] of this.Votes) {
+ if (!this.Direction) {
+ this.Direction = v.VoteDirection;
+ }
+ if (!this.MeritRequest) {
+ this.MeritRequest = v.Request;
+ }
+ if (v.VoteDirection != this.Direction || v.Request != this.MeritRequest) {
+ valid = false;
+ }
+ }
+ return valid;
+ }
+ Total(rocket: Rocket): number {
+ let total = 0;
+ if (this.BasicValidation()) {
+ for (let [_, v] of this.Votes) {
+ total += rocket.VotePowerForPubkey(v.Pubkey);
+ }
+ }
+
+ return total;
+ }
+ TotalPercent(rocket: Rocket): number {
+ let result = undefined;
+ let total = this.Total(rocket);
+ return total / rocket.TotalVotePower();
+ }
+ constructor(votes: Map) {
+ this.Votes = votes;
+ this.Direction = undefined;
+ this.MeritRequest = undefined;
+ if (!this.BasicValidation()) {
+ throw new Error('invalid votes detected');
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/lib/event_helpers/rockets.ts b/src/lib/event_helpers/rockets.ts
index 6604f53..833fad2 100644
--- a/src/lib/event_helpers/rockets.ts
+++ b/src/lib/event_helpers/rockets.ts
@@ -2,29 +2,144 @@ import { NDKEvent, type NDKTag } from '@nostr-dev-kit/ndk';
export class Rocket {
Event: NDKEvent;
- Name():string {
- return this.Event.dTag!
+ Name(): string {
+ return this.Event.dTag!;
}
- VotePowerForPubkey(pubkey:string):number {
- let votepower = 0
+ VotePowerForPubkey(pubkey: string): number {
+ let votepower = 0;
if (this.Event.pubkey == pubkey) {
//todo: calculate votepower for pubkey based on approved merit requests
- votepower++
+ votepower++;
}
- return votepower
+ return votepower;
}
- constructor(event:NDKEvent) {
+ TotalVotePower(): number {
+ //todo: calculate votepower for pubkey based on approved merit requests
+ return 1;
+ }
+ ApprovedMeritRequests(): Map {
+ let amr = new Map();
+ for (let m of this.Event.getMatchingTags('merit')) {
+ if (m && m.length == 2) {
+ let _amr = new RocketAMR(m[1]);
+ amr.set(_amr.ID, _amr);
+ }
+ }
+ return amr;
+ }
+ AppendAMR(amrProof: NDKEvent): NDKEvent | undefined {
+ //todo
+ let request: NDKEvent | undefined = undefined;
+ let votes: NDKEvent[] = [];
+ let _request = amrProof.getMatchingTags('request');
+ if (_request.length == 1) {
+ try {
+ request = JSON.parse(_request[0][1]);
+ } catch {}
+ }
+ for (let v of amrProof.getMatchingTags('vote')) {
+ try {
+ votes.push(JSON.parse(v[1]));
+ } catch {}
+ }
+ return
+ //add the AMR to the rocket event, and also add a proof
+ }
+ UpsertProduct(id: string, price: number, maxSales?: number): NDKEvent {
+ let event = new NDKEvent(this.Event.ndk, this.Event.rawEvent());
+ event.created_at = Math.floor(new Date().getTime() / 1000);
+ let existingProducts = this.CurrentProducts();
+ let purchases = JSON.stringify([]);
+ let existingProduct = existingProducts.get(id);
+ if (existingProduct) {
+ purchases = existingProduct.PurchasesJSON();
+ }
+ event.tags.push([
+ 'product',
+ `${id}:${price}:${event.created_at}:${maxSales}`,
+ 'wss://relay.nostrocket.org',
+ purchases
+ ]);
+ updateIgnitionAndParentTag(event);
+ return event;
+ }
+ CurrentProducts(): Map {
+ return getMapOfProductsFromRocket(this.Event);
+ }
+
+ constructor(event: NDKEvent) {
this.Event = event;
}
}
+function updateIgnitionAndParentTag(event: NDKEvent) {
+ let existingIgnition = event.getMatchingTags('ignition');
+ //let existingParent = rocket.getMatchingTags("parent")
+ let existing = [];
+ for (let t of event.tags) {
+ existing.push(t);
+ }
+ event.tags = [];
+ for (let t of existing) {
+ if (t[0] !== 'ignition' && t[0] !== 'parent') {
+ event.tags.push(t);
+ }
+ }
+ if (existingIgnition.length > 1) {
+ throw new Error('too many ignition tags!');
+ }
+ if (existingIgnition.length == 0) {
+ event.tags.push(['ignition', event.id]);
+ }
+ if (existingIgnition.length == 1) {
+ if (existingIgnition[0][1].length == 64) {
+ event.tags.push(existingIgnition[0]);
+ }
+ if (existingIgnition[0][1] == 'this') {
+ event.tags.push(['ignition', event.id]);
+ }
+ }
+ event.tags.push(['parent', event.id]);
+}
+
+export class RocketAMR {
+ ID: string;
+ Pubkey: string;
+ LeadTime: number;
+ LeadTimeUpdate: number;
+ Merits: number;
+ Valid(): boolean {
+ let valid = true;
+ if (!(this.ID.length == 64 && this.Pubkey.length == 64 && this.Merits)) {
+ valid = false;
+ }
+ return valid;
+ }
+ constructor(meritString: string) {
+ let split = meritString.split(':');
+ if (split.length == 5) {
+ this.Pubkey = split[0];
+ this.ID = split[1];
+ this.LeadTime = parseInt(split[2], 10);
+ this.LeadTimeUpdate = parseInt(split[3], 10);
+ this.Merits = parseInt(split[4], 10);
+ }
+ }
+}
+
export class RocketProduct {
ID: string;
Price: number;
ValidAfter: number; //unix time
MaxPurchases: number;
Purchases: Map;
-
+ PurchasesJSON(): string {
+ let purchases = [];
+ for (let [_, p] of this.Purchases) {
+ purchases.push(`${p.ZapID}:${p.BuyerPubkey}:${p.WitnessedAt}`);
+ }
+ return JSON.stringify(purchases);
+ }
constructor(tag: NDKTag) {
this.Purchases = new Map();
this.ID = tag[1].split(':')[0];
@@ -138,23 +253,23 @@ function getZapRequest(zapReceipt: NDKEvent): NDKEvent | undefined {
}
function getZapAmount(zapRequest?: NDKEvent): number {
- return getNumberFromTag("amount", zapRequest)
+ return getNumberFromTag('amount', zapRequest);
}
-export function getNumberFromTag(tag:string, event?: NDKEvent): number {
+export function getNumberFromTag(tag: string, event?: NDKEvent): number {
let amountTag = event?.getMatchingTags(tag);
if (amountTag && amountTag[0] && amountTag[0][1]) {
try {
let amount = parseInt(amountTag[0][1], 10);
- return amount
+ return amount;
} catch {
- console.log("ERROR: could not find number in tag: ", tag, event)
+ console.log('ERROR: could not find number in tag: ', tag, event);
}
}
- return 0
+ return 0;
}
-export function isValidUrl(string:string):boolean {
+export function isValidUrl(string: string): boolean {
try {
new URL(string);
return true;
@@ -163,6 +278,6 @@ export function isValidUrl(string:string):boolean {
}
}
-export function RocketATagFilter(rocket:NDKEvent):string {
- return `31108:${rocket.pubkey}:${rocket.dTag}`
-}
\ No newline at end of file
+export function RocketATagFilter(rocket: NDKEvent): string {
+ return `31108:${rocket.pubkey}:${rocket.dTag}`;
+}
diff --git a/src/routes/rockets/merits/[merit]/+page.svelte b/src/routes/rockets/merits/[merit]/+page.svelte
index 8b11d86..083834f 100644
--- a/src/routes/rockets/merits/[merit]/+page.svelte
+++ b/src/routes/rockets/merits/[merit]/+page.svelte
@@ -11,7 +11,7 @@
let meritRequestID = $page.params.merit;
- let meritRequest:MeritRequest | undefined;
+ let meritRequest:MeritRequest | undefined = undefined;
$: {
if (meritRequestID.length == 64 && !meritRequest) {