Merge branch 'MASTER' into fix-style

This commit is contained in:
Angelica Willianto
2024-08-14 18:03:28 +07:00
23 changed files with 512 additions and 343 deletions

View File

@@ -1,6 +1,17 @@
# Hypergolic
A nostrocket client
A nostr client that implements the nostrocket [NIPS](https://github.com/nostrocket/NIPS).
## Developing
```bash
npm install
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Pull Requests
@@ -12,6 +23,8 @@ Pull request should contain only ONE commit that solves exactly ONE problem. The
The commit message MUST be a short summary of the problem being solved, usually this should be the same as title of the problem from the github or nostrocket issue tracker.
Do not send pull requests unless they are ready to merge, no "work in progress" pull requests.
## Style Guide
Avoid uneccessary whitespace changes. Whitespace changes make it difficult to see what code was really changed.
@@ -22,25 +35,8 @@ https://www.shadcn-svelte.com/docs
## Getting Paid
If you want to get paid for working on this project, demonstrate your capability by solving some problems and adhering to the instructions in this readme. After you have 2-3 PR's merged, DM gsovereignty on nostr to have a conversation about paid work.
Once your pull request has been merged, go to nostrocket.org and submit a Merit Request.
## Developing
Upon your Merit Request being approved by the existing contributors to Nostrocket, you will be able to sell your Approved Merit Request (there are people wanting to buy these for sats).
`npm install`
```bash
npm run dev
# or start the server and open the app in a new browser tab
npm run dev -- --open
```
## Building
To create a production version of your app:
```bash
npm run build
```
You can preview the production build with `npm run preview`.
Ask in the Telegram group or on Nostr if you need help or have questions.

View File

@@ -10,10 +10,29 @@
import validate from 'bitcoin-address-validation';
import type { Rocket } from '@/event_helpers/rockets';
import { derived } from 'svelte/store';
import { onDestroy } from 'svelte';
import type { NDKEventStore, ExtendedBaseType } from '@nostr-dev-kit/ndk-svelte';
let bitcoinAddress: string;
export let rocket: Rocket;
let associations: NDKEventStore<ExtendedBaseType<NDKEvent>> | undefined = undefined;
$: {
if ($currentUser && !associations) {
associations = $ndk.storeSubscribe(
[{ authors: [$currentUser.pubkey], kinds: [1413 as number] }],
{
subId: `1413-${$currentUser.pubkey}`
}
);
}
}
onDestroy(() => {
associations?.unsubscribe();
});
let associatedAddresses = derived(currentUser, ($currentUser) => {
let addresses: Set<string> = new Set();
if ($currentUser) {
@@ -40,6 +59,7 @@
let event = new NDKEvent($ndk);
event.kind = 1413;
event.tags.push(['onchain', address]);
event.tags.push(rocket.ATag());
//todo: let user specify a rocket
console.log('todo: let user specify a rocket');
event
@@ -63,13 +83,23 @@ their Merits.
Merit purchases must be conducted with a Bitcoin address that is associated with your pubkey,
otherwise you will not recieve the Merits upon payment.
</div>
{#if $associatedAddresses.size == 0}You do not have any registered addresses{:else}
Your registered addresses:
{#if ($associatedAddresses.size == 0 && !associations) || (associations && $associations.length == 0)}You
do not have any registered addresses{:else if associations && $associations && $associations.length > 0}
<h4 class="text-lg font-bold dark:text-white">Pending Additions</h4>
<ul class="m-2 flex flex-col">
{#each $associations as event}<li class="list-item list-disc">
{event.getMatchingTags('onchain')[0][1]}
</li>{/each}
</ul>
{:else}
<h4 class="text-lg font-bold dark:text-white">Your registered addresses</h4>
<ul class="m-2 flex flex-col">
{#each $associatedAddresses as address}<li class="list-item list-disc">{address}</li>{/each}
</ul>
{/if}
Add a new address now
<h4 class="text-lg font-bold dark:text-white">Add a new address now</h4>
<div class="flex">
<InputBitcoinAddress bind:bitcoinAddress /><Button
on:click={() => publish(bitcoinAddress)}

View File

@@ -0,0 +1,122 @@
<script lang="ts">
import * as Card from '@/components/ui/card';
import * as Table from '@/components/ui/table';
import { BitcoinAssociation, Rocket } from '@/event_helpers/rockets';
import { getCuckPrice } from '@/helpers';
import { ndk } from '@/ndk';
import { getBalance } from '@/stores/bitcoin';
import { NDKKind } from '@nostr-dev-kit/ndk';
import { Avatar, Name } from '@nostr-dev-kit/ndk-svelte-components';
import { onDestroy, onMount } from 'svelte';
import { derived, writable } from 'svelte/store';
export let rocket: Rocket;
let cuckprice = 0;
let associations = writable(new Map<string, BitcoinAssociation>());
onMount(() => {
getCuckPrice().then((price) => {
if (price) cuckprice = price;
});
associations.set(rocket.BitcoinAssociations());
$associations.forEach((a) => {
if (a.Address) {
getBalance(a.Address)
.then((v) => {
a.Balance = v;
associations.update((existing) => {
existing.set(a.Address!, a);
return existing;
});
})
.catch((err) => {
console.log(err);
});
}
});
});
let amounts = derived(associations, ($associations) => {
let amounts = new Map<string, number>();
for (let [_, a] of $associations) {
if (a.Balance > 0) {
let existing = amounts.get(a.Pubkey);
if (!existing) {
existing = 0;
}
existing += a.Balance;
amounts.set(a.Pubkey, existing);
}
}
return amounts;
});
let total = derived(amounts, ($amounts) => {
let t = 0;
for (let [_, a] of $amounts) {
t += a;
}
return t;
});
</script>
<Card.Root class="sm:col-span-3">
<Card.Header class="px-7">
<Card.Title>Sponsors</Card.Title>
<Card.Description
>These people want to sponsor Contributors working on {rocket.Name()}.</Card.Description
>
</Card.Header>
<Card.Content>
<Table.Root>
<Table.Header>
<Table.Row>
<Table.Head class="w-[200px]">Sponsor</Table.Head>
<Table.Head class="hidden text-left md:table-cell">Amount (Sats)</Table.Head>
<Table.Head class="hidden text-left md:table-cell">CuckLoserBucks</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{#each $amounts as [npub, amount], _ (npub)}
<Table.Row>
<Table.Cell>
<div class="flex flex-nowrap">
<Avatar
ndk={$ndk}
pubkey={npub}
class="h-10 w-10 flex-none rounded-full object-cover"
/>
<Name
ndk={$ndk}
pubkey={npub}
class="hidden max-w-32 truncate p-2 md:inline-block"
/>
</div>
</Table.Cell>
<Table.Cell class="hidden text-left md:table-cell">
{amount.toLocaleString()}
</Table.Cell>
<Table.Cell class="hidden text-left md:table-cell">
${Math.floor((amount / 100000000) * cuckprice).toLocaleString()}
</Table.Cell>
</Table.Row>
{/each}
{#if $total > 0}
<Table.Row
class=" bg-violet-200 hover:bg-violet-300 dark:bg-violet-950 dark:hover:bg-violet-900"
>
<Table.Cell>TOTAL</Table.Cell>
<Table.Cell class="hidden text-left md:table-cell">
{$total.toLocaleString()}
</Table.Cell>
<Table.Cell class="hidden text-left md:table-cell">
${Math.floor(($total / 100000000) * cuckprice).toLocaleString()}
</Table.Cell>
</Table.Row>
{/if}
</Table.Body>
</Table.Root>
</Card.Content>
</Card.Root>

View File

@@ -1,86 +0,0 @@
<script lang="ts">
import * as Card from '@/components/ui/card';
import * as Table from '@/components/ui/table';
import { BitcoinAssociation, Rocket } from '@/event_helpers/rockets';
import { ndk } from '@/ndk';
import { getBalance } from '@/stores/bitcoin';
import { NDKKind } from '@nostr-dev-kit/ndk';
import { Avatar, Name } from '@nostr-dev-kit/ndk-svelte-components';
import { onDestroy, onMount } from 'svelte';
export let rocket: Rocket;
let _associationRequests = $ndk.storeSubscribe(
[{ '#a': [`31108:${rocket.Event.author.pubkey}:${rocket.Name()}`], kinds: [1413 as NDKKind] }],
{
subId: `${rocket.Name()}_bitcoin_associations`
}
);
onDestroy(() => {
_associationRequests?.unsubscribe();
});
let addresses = new Map<string, BitcoinAssociation>();
onMount(() => {
addresses = rocket.BitcoinAssociations();
addresses.forEach((a) => {
if (a.Address) {
getBalance(a.Address)
.then((v) => {
a.Balance = v;
addresses.set(a.Address!, a);
addresses = addresses;
})
.catch((err) => {
console.log(err);
});
}
});
});
</script>
<Card.Root class="sm:col-span-3">
<Card.Header class="px-7">
<Card.Title>Registered Bitcoin Addresses</Card.Title>
<Card.Description
>These people have registered a Bitcoin address and want to sponsor Contributors working on {rocket.Name()}</Card.Description
>
</Card.Header>
<Card.Content>
<Table.Root>
<Table.Header>
<Table.Row>
<Table.Head class="w-[200px]">Sponsor</Table.Head>
<Table.Head class="hidden text-left md:table-cell">Amount (Sats)</Table.Head>
<Table.Head class="table-cell">Address</Table.Head>
</Table.Row>
</Table.Header>
<Table.Body>
{#each addresses as [address, ba], _ (address)}
<Table.Row>
<Table.Cell>
<div class="flex flex-nowrap">
<Avatar
ndk={$ndk}
pubkey={ba.Pubkey}
class="h-10 w-10 flex-none rounded-full object-cover"
/>
<Name
ndk={$ndk}
pubkey={ba.Pubkey}
class="hidden max-w-32 truncate p-2 md:inline-block"
/>
</div>
</Table.Cell>
<Table.Cell class="hidden text-left md:table-cell">
{ba.Balance.toLocaleString()}
</Table.Cell>
<Table.Cell class="table-cell">{ba.Address}</Table.Cell>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
</Card.Content>
</Card.Root>

View File

@@ -12,11 +12,12 @@
import type NDKSvelte from '@nostr-dev-kit/ndk-svelte';
import { Terminal } from 'lucide-svelte';
import Todo from './Todo.svelte';
import { isValidUrl } from '@/event_helpers/rockets';
import { isValidUrl, Rocket } from '@/event_helpers/rockets';
import CalculateSats from './CalculateSats.svelte';
import { isGitHubUrl, parseProblem } from '@/helpers';
import Login from './Login.svelte';
export let rocketEvent: NDKEvent;
export let rocket: Rocket;
let problem: string = '';
let solution: string = '';
@@ -78,17 +79,29 @@
e.created_at = Math.floor(new Date().getTime() / 1000);
e.tags.push(['problem', 'text', problem]);
if (solution.length > 0) {
e.tags.push(['solution', 'url', solution]);
try {
let url = new URL(solution);
e.tags.push(['solution', 'url', url.toString()]);
} catch {
e.tags.push(['solution', 'text', solution]);
}
}
e.tags.push(['a', `31108:${rocketEvent.pubkey}:${rocketEvent.dTag}`]);
e.tags.push(['a', `31108:${rocket.Event.pubkey}:${rocket.Event.dTag}`]);
e.tags.push(['merits', merits.toString(10)]);
e.tags.push(['sats', sats]);
console.log(e.rawEvent());
e.publish().then((x) => {
console.log(x);
open = false;
//goto(`${base}/rockets/${getRocketURL(e)}`);
});
e.publish()
.then((x) => {
console.log(x);
console.log('todo: publish a kind 1 and tag the rocket and author');
open = false;
//goto(`${base}/rockets/${getRocketURL(e)}`);
})
.catch(() => {
alert(
"something went wrong, copy/paste your data and refresh then try again if you don't see your merit request in the rocket dashboard (we r so early, there will be blugs)"
);
});
}
</script>
@@ -140,7 +153,7 @@
<CalculateSats on:result={(event) => (sats = event.detail)} />
</div>
<div class="flex items-center space-x-2">
<!-- <div class="flex items-center space-x-2">
<Checkbox id="sell" bind:checked={wts} aria-labelledby="terms-label" />
<Label
id="terms-label"
@@ -149,9 +162,9 @@
>
I want Sats not Merits
</Label>
</div>
</div> -->
{#if wts}
<!-- {#if wts}
Your Merits will be auctioned to potential sponsors as soon as it is approved, enabling you
to be paid in Sats for your work. Tip: you don't have to decide right now, you can do this
at any time.
@@ -160,15 +173,19 @@
<Label for="sats" class="col-span-2 text-right">Auction Reserve Price (Sats)</Label>
<Input bind:value={minimum} id="price" placeholder="Reserve Price" class="col-span-1" />
</div>
{/if}
{/if} -->
</div>
<Dialog.Footer>
<Button
on:click={() => {
publish($ndk);
}}
type="submit">Publish</Button
>
{#if $currentUser}
<Button
on:click={() => {
publish($ndk);
}}
type="submit">Publish</Button
>
{:else}
<Login />
{/if}
</Dialog.Footer>
<Todo
text={['remove white border on focus so that the validation indication color can be seen']}

View File

@@ -3,7 +3,9 @@
import { page } from '$app/stores';
import { Badge } from '@/components/ui/badge';
import Separator from '@/components/ui/separator/separator.svelte';
import { Mail, Package, Pyramid, Rocket, Users } from 'lucide-svelte';
import { currentUser } from '@/stores/session';
import { GitBranch, HelpCircle, Mail, Package, Pyramid, Rocket, Users } from 'lucide-svelte';
import { GitAltBrand, TelegramBrand } from 'svelte-awesome-icons';
let iconClass = 'h-5 w-5 md:h-4 md:w-4';
@@ -16,22 +18,44 @@
};
</script>
{#if $currentUser}
<a href="{base}/inbox" class={getClass('inbox')}>
<Mail class={iconClass} />
Inbox
<Badge class="ml-auto flex h-6 w-6 shrink-0 items-center justify-center rounded-full">1</Badge>
</a>
<Separator class="dark:bg-slate-700" />
{/if}
<a href="{base}/rockets" class={getClass('rockets')}>
<Rocket class={iconClass} />
Rockets
</a>
<a href="##" class={getClass('problems')}>
<Pyramid class={iconClass} />
Problem Tracker
<Badge class="ml-auto flex h-6 w-6 shrink-0 items-center justify-center rounded-full">6</Badge>
</a>
<a href="{base}/products" class={getClass('products')}>
<Package class={iconClass} />
Products
</a>
<a href="##" class={getClass('people')}>
<a href="{base}/problems" class={getClass('problems')}>
<Pyramid class={iconClass} />
Problem Tracker
<!-- <Badge class="ml-auto flex h-6 w-6 shrink-0 items-center justify-center rounded-full">6</Badge> -->
</a>
<!-- <a href="##" class={getClass('people')}>
<Users class={iconClass} />
People
</a> -->
<Separator class="dark:bg-slate-700" />
<a href="https://github.com/nostrocket/hypergolic" class={getClass('_')}>
<GitAltBrand class={iconClass} />
Source
</a>
<a href="https://t.me/nostrocket" class={getClass('_')}>
<TelegramBrand class={iconClass} />
Telegram Group
</a>
<a href="{base}/help" class={getClass('help')}>
<HelpCircle class={iconClass} />
Help
</a>
<Separator class="my-2" />
<a href="##" class={getClass('inbox')}>

View File

@@ -13,6 +13,7 @@
import { Avatar, Name } from '@nostr-dev-kit/ndk-svelte-components';
import { onDestroy } from 'svelte';
import { derived, writable } from 'svelte/store';
import CreateMeritRequest from './CreateMeritRequest.svelte';
//export let rocket: NDKEvent;
export let rocket: Rocket; // = new Rocket(rocket);
@@ -92,11 +93,12 @@
</script>
<Card.Root class="sm:col-span-3">
<Card.Header class="px-7">
<Card.Header class="pb-3">
<Card.Title>Merit Requests</Card.Title>
<Card.Description>Merit Requests</Card.Description>
</Card.Header>
<Card.Content>
<CreateMeritRequest {rocket} />
<Table.Root>
<Table.Header>
<Table.Row>

View File

@@ -19,6 +19,7 @@
import { Alert } from '@/components/ui/alert';
import { currentUser } from '@/stores/session';
import MeritComment from './MeritComment.svelte';
import { Description } from 'formsnap';
export let merit: MeritRequest;
//export let rocket: NDKEvent;
@@ -26,7 +27,7 @@
let result: VoteDirection | undefined;
export let parsedRocket:Rocket;
export let parsedRocket: Rocket;
let _votes = $ndk.storeSubscribe(
{ '#a': [RocketATagFilter(parsedRocket.Event)], '#e': [merit.ID], kinds: [1410 as NDKKind] },
@@ -52,7 +53,7 @@
}
$: referenceTime = cuckPrice
? formatReferenceTime(((merit.Sats / 100000000) * cuckPrice) / 70)
? formatReferenceTime(((merit.Sats / 100000000) * cuckPrice) / 50)
: '...';
let votes = derived(_votes, ($_votes) => {
@@ -118,9 +119,9 @@
<div class="flex flex-nowrap justify-between">
<Card.Title>
{merit.Problem().split('\n')[0]}
</Card.Title>{#if merit.Solution()}<a
</Card.Title>{#if merit.SolutionURL()}<a
class="flex flex-nowrap text-orange-500 underline decoration-orange-500"
href={merit.Solution()}>View Solution <ExternalLink size={18} class="m-1" /></a
href={merit.SolutionURL()}>View Solution <ExternalLink size={18} class="m-1" /></a
>{/if}
</div>
<div class="flex flex-nowrap">
@@ -131,87 +132,101 @@
/>
<Name ndk={$ndk} pubkey={merit.Pubkey} class="inline-block max-w-32 truncate p-2" />
</div>
<Card.Content class="p-6 text-sm">
<div class="grid gap-3">
<div class="font-semibold">Merit Request Details</div>
<ul class="grid gap-3">
<li class="flex items-center justify-between">
<span class="text-muted-foreground"> Number of Merits being requested </span>
<span>{merit.Merits.toLocaleString()}</span>
</li>
<li class="flex items-center justify-between">
<span class="text-muted-foreground">
Value in Sats at the time the request was made
</span>
<span>{merit.Sats.toLocaleString()}</span>
</li>
<li class="flex items-center justify-between">
<span class="text-muted-foreground">
Approximate value of {merit.Sats.toLocaleString()} sats in CuckLoserBucks
</span>
<span>${cuckPrice ? ((merit.Sats / 100000000) * cuckPrice).toFixed(2) : 'Loading'}</span
>
</li>
</ul>
<Separator class="my-4" />
<div class="grid grid-cols-2 gap-4">
<div class="grid gap-3">
<div class="font-semibold">Analysis</div>
<span class="grid gap-0.5 not-italic text-muted-foreground">
A competent freelance developer earns $70 CuckLoserBucks an hour (on average). Using
this rate, the contributor is claiming to have spent about {referenceTime} working on this.
</span>
</div>
<div class="grid auto-rows-max gap-3">
<div class="font-semibold">Reference Time</div>
<div class="text-muted-foreground">{referenceTime}</div>
</div>
</div>
<Separator class="my-4" />
<div class="font-semibold">Votes</div>
{#if $votes.size == 0}<Alert
><Info />Waiting for existing <span class="italic">{parsedRocket.Name()}</span> Merit
holders to vote</Alert
>
{/if}
<Table.Root>
<Table.Body>
{#each $votes as [id, vote], _ (id)}
<Table.Row
on:click={() => {
console.log(vote.Event.rawEvent());
}}
class="cursor-pointer {vote.VoteDirection == 'ratify'
? 'bg-lime-600'
: 'bg-red-700'} {vote.VoteDirection == 'ratify'
? 'hover:bg-lime-700'
: 'hover:bg-red-800'}"
>
<Table.Cell>
<div class="flex flex-nowrap">
<Avatar
ndk={$ndk}
pubkey={vote.Pubkey}
class="h-10 w-10 flex-none rounded-full object-cover"
/>
<Name
ndk={$ndk}
pubkey={vote.Pubkey}
class="inline-block max-w-32 truncate p-2"
/>
</div>
</Table.Cell>
<Table.Cell class="hidden text-left md:table-cell">{vote.VoteDirection}</Table.Cell>
<Table.Cell class="table-cell text-right"
>{unixToRelativeTime(vote.TimeStamp * 1000)}</Table.Cell
>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
</div></Card.Content
>
{#if merit.SolutionText()}
{merit.SolutionText()?.trim()}
{/if}
<Card.Description></Card.Description>
</Card.Header>
<Card.Content class="p-6 text-sm">
<div class="grid gap-3">
<div class="font-semibold">Merit Request Details</div>
<ul class="grid gap-3">
<li class="flex items-center justify-between">
<span class="text-muted-foreground"> Number of Merits being requested </span>
<span>{merit.Merits.toLocaleString()}</span>
</li>
<li class="flex items-center justify-between">
<span class="text-muted-foreground">
Value in Sats at the time the request was made
</span>
<span>{merit.Sats.toLocaleString()}</span>
</li>
<li class="flex items-center justify-between">
<span class="text-muted-foreground">
Approximate value of {merit.Sats.toLocaleString()} sats in CuckLoserBucks
</span>
<span>${cuckPrice ? ((merit.Sats / 100000000) * cuckPrice).toFixed(2) : 'Loading'}</span>
</li>
</ul>
<Separator class="my-4" />
<div class="grid grid-cols-2 gap-4">
<div class="grid gap-3">
<div class="font-semibold">Analysis</div>
<span class="grid gap-0.5 not-italic text-muted-foreground">
<p class="m-1 text-justify">
To make it easier to compare the value of each contribution we normalize the hourly
rate to $50 CuckLoserBucks an hour.
</p>
<p class="m-1 text-justify">
At this rate, the contributor is claiming to have worked for {referenceTime} solving this
problem.
</p>
</span>
</div>
<div class="grid auto-rows-max gap-3">
<div class="font-semibold">Reference Time</div>
<div class="text-muted-foreground">{referenceTime}</div>
</div>
</div>
<Separator class="my-4" />
<div class="font-semibold">Votes</div>
{#if $votes.size == 0}<Alert
><Info />Waiting for existing <span class="italic">{parsedRocket.Name()}</span> Merit holders
to vote</Alert
>
{/if}
<Table.Root>
<Table.Body>
{#each $votes as [id, vote], _ (id)}
<Table.Row
on:click={() => {
console.log(vote.Event.rawEvent());
}}
class="cursor-pointer {vote.VoteDirection == 'ratify'
? 'bg-lime-600'
: 'bg-red-700'} {vote.VoteDirection == 'ratify'
? 'hover:bg-lime-700'
: 'hover:bg-red-800'}"
>
<Table.Cell>
<div class="flex flex-nowrap">
<Avatar
ndk={$ndk}
pubkey={vote.Pubkey}
class="h-10 w-10 flex-none rounded-full object-cover"
/>
<Name
ndk={$ndk}
pubkey={vote.Pubkey}
class="inline-block max-w-32 truncate p-2"
/>
</div>
</Table.Cell>
<Table.Cell class="hidden text-left md:table-cell">{vote.VoteDirection}</Table.Cell>
<Table.Cell class="table-cell text-right"
>{unixToRelativeTime(vote.TimeStamp * 1000)}</Table.Cell
>
</Table.Row>
{/each}
</Table.Body>
</Table.Root>
</div>
<a href="#" class="text-xs" on:click={() => console.log(merit.Event.rawEvent())}
>print to console</a
></Card.Content
>
<Card.Footer class="flex flex-row justify-center border-t px-6 py-3 text-center {background}">
{#if merit.IncludedInRocketState(parsedRocket)}
<span class="scroll-m-20 text-lg font-semibold tracking-tight md:text-xl">APPROVED</span>

View File

@@ -10,20 +10,19 @@
import { currentUser } from '@/stores/session';
import Login from './Login.svelte';
import { NDKPrivateKeySigner, NDKUser } from '@nostr-dev-kit/ndk';
import { TelegramBrand } from 'svelte-awesome-icons';
let email: string;
let email: string = '';
let fax: string = '';
$: emailInValid = true;
$: emailError = '';
$: emailError = 'Email is invalid';
const emailRegex = /^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$/;
$: if (email) {
if (!emailRegex.test(email)) {
emailInValid = true;
emailError = 'Email is invalid';
} else {
emailInValid = false;
emailError = '';
}
$: {
emailError = emailRegex.test(email) ? '' : 'Email is invalid';
}
$: {
emailInValid = emailRegex.test(email) ? false : true;
}
async function Subscribe() {
@@ -84,14 +83,15 @@
variant="nostr"
class="flex h-8 shrink-0 items-center justify-center rounded-sm"
>
Nostrocket is totally not ready yet but whatever
u r so early
</Badge>
</Dialog.Trigger>
<Dialog.Content>
<Dialog.Header>
<Dialog.Title>Subscribe for Updates</Dialog.Title>
<Dialog.Description>
Subscribe now and we'll ping you when there are new releases/features
Nostrocket is under active development, many things are broken. Subscribe now and we'll ping
you when there are new releases and new features.
</Dialog.Description>
<div class="flex flex-col gap-4 py-4">
{#if $currentUser}
@@ -101,19 +101,32 @@
{/if}
<Separator />
<span class="ml-auto mr-auto flex"
>If you don't use nostr, you can subscribe to updates with an email address instead</span
>If you don't use nostr, that's SAD! Whatever, I guess we can fax you or email you or
something</span
>
<div class="grid grid-cols-4 items-center gap-4">
<Label for="email" class="text-right">Email</Label>
<Input bind:value={email} id="email" placeholder="Your email" class="col-span-3" />
</div>
{#if emailError}
<!-- {#if emailError}
<div class="ml-4 p-0 text-sm text-red-500">{emailError}</div>
{/if} -->
<div class="grid grid-cols-4 items-center gap-4">
<Label for="fax" class="text-right">Fax number</Label>
<Input bind:value={fax} id="email" placeholder="Your fax number" class="col-span-3" />
</div>
{#if fax.length > 0}
<iframe src="https://www.youtube.com/embed/dQw4w9WgXcQ?autoplay=1" allow="autoplay"
></iframe>
{/if}
</div>
<Button disabled={emailInValid} on:click={SubmitEmailAndSubscribe}
>Please email me with updates</Button
>{emailError ? emailError : "I'm lame, please email me with updates"}</Button
>
<Separator />
<a href="https://t.me/nostrocket" class="flex flex-nowrap">
<TelegramBrand class="mr-2" /> Join the Telegram Group
</a>
</Dialog.Header>
</Dialog.Content>
</Dialog.Root>

View File

@@ -5,6 +5,8 @@
import { ndk } from '@/ndk';
import { derived, writable } from 'svelte/store';
import ProductGroup from './ProductGroup.svelte';
import CreateNewProduct from './CreateNewProduct.svelte';
import CreateMeritRequest from './CreateMeritRequest.svelte';
export let rocket: Rocket;
export let unratifiedZaps: Map<string, number>;
@@ -51,10 +53,11 @@
<Card.Title>Products and Purchases</Card.Title>
<Card.Description></Card.Description>
</Card.Header>
<Card.Content class="grid grid-cols-1 gap-2">
{#each $groups as [identifier, products] (identifier)}
<ProductGroup {products} {rocket} bind:unratifiedZaps />
{/each}
</Card.Content>
<Card.Footer></Card.Footer>
<Card.Footer><CreateNewProduct rocketEvent={rocket.Event} /></Card.Footer>
</Card.Root>

View File

@@ -2,17 +2,15 @@
import * as Breadcrumb from '$lib/components/ui/breadcrumb/index.js';
import Button from '@/components/ui/button/button.svelte';
import * as Card from '@/components/ui/card';
import { Rocket } from '@/event_helpers/rockets';
import type { NDKEvent } from '@nostr-dev-kit/ndk';
import CreateMeritRequest from './CreateMeritRequest.svelte';
import CreateNewProduct from './CreateNewProduct.svelte';
import BitcoinAssociations from './AssociatedBitcoinAddresses.svelte';
import MeritRequests from './MeritRequests.svelte';
import MeritsAndSatflow from './MeritsAndSatflow.svelte';
import ProductFomo from './ProductFomo.svelte';
import ProposedProducts from './ProposedProducts.svelte';
import Todo from './Todo.svelte';
import UpdateMission from './UpdateMission.svelte';
import { Rocket } from '@/event_helpers/rockets';
import BitcoinAssociations from './BitcoinAssociations.svelte';
export let rocket: NDKEvent;
@@ -47,8 +45,6 @@
<Card.Title class="pb-4">Actions</Card.Title>
<Card.Description class="flex flex-wrap gap-2">
<UpdateMission rocketEvent={rocket} />
<CreateNewProduct rocketEvent={rocket} />
<CreateMeritRequest rocketEvent={rocket} />
<Button
on:click={() => {
console.log(rocket.rawEvent());
@@ -58,10 +54,5 @@
</Card.Header>
<Card.Footer></Card.Footer>
</Card.Root>
<Todo
className="sm:col-span-3"
text={['delete rocket (if current user is rocket creator) - publish deletion request']}
/>
</main>
</div>

View File

@@ -32,19 +32,19 @@
.publish()
.then((x) => {
console.log(x);
if (direction === 'ratify') {
let content = `I've voted to ratify your merit request! ${merit.Problem()} \n\n ${merit.SolutionURL() ? merit.SolutionURL() : ''}`;
prepareMeritNoteEvent({
ndk,
merit,
content
})
.publish()
.then((x) => {
console.log(x);
});
}
});
if (direction === 'ratify') {
let content = `I've voted to ratify your merit request! ${merit.Problem()} \n\n ${merit.Solution() ? merit.Solution() : ''}`;
prepareMeritNoteEvent({
ndk,
merit,
content
})
.publish()
.then((x) => {
console.log(x);
});
}
}
$: currentUserHasVotepower = false;

View File

@@ -23,7 +23,7 @@ export class MeritRequest {
}
return _problem;
}
Solution(): URL | undefined {
SolutionURL(): URL | undefined {
let _solution: URL | undefined = undefined;
for (let solution of this.Event.getMatchingTags('solution')) {
if (solution && solution.length > 2 && solution[1] == 'url') {
@@ -34,6 +34,15 @@ export class MeritRequest {
}
return _solution;
}
SolutionText(): string | undefined {
let _solution: string | undefined = undefined;
for (let solution of this.Event.getMatchingTags('solution')) {
if (solution && solution.length > 2 && solution[1] == 'text') {
_solution = solution[2];
}
}
return _solution;
}
IncludedInRocketState(rocket: Rocket): boolean {
let included = rocket.ApprovedMeritRequests();
return Boolean(included.get(this.ID));

View File

@@ -9,6 +9,9 @@ import { BloomFilter } from 'bloomfilter';
export class Rocket {
Event: NDKEvent;
ATag(): NDKTag {
return ['a', `31108:${this.Event.pubkey}:${this.Event.dTag}`];
}
private Bloom(): BloomFilter {
let b = new BloomFilter(
64 * 256, // bits to allocate.

View File

@@ -82,17 +82,24 @@ export function formatSats(sats: number): string {
}
}
export async function getCuckPrice(): Promise<number | Error> {
try {
export async function getCuckPrice(): Promise<number> {
return new Promise((resolve, reject) => {
var url = 'https://api.coindesk.com/v1/bpi/currentprice.json';
var symbol = 'USD';
const data = await fetch(url);
const json = await data.json();
const cuckPrice = parseFloat(json.bpi[symbol].rate.replace(/,/g, ''));
return cuckPrice;
} catch (e) {
return e as Error;
}
fetch(url)
.then((r) => {
r.json()
.then((j) => {
resolve(parseFloat(j.bpi[symbol].rate.replace(/,/g, '')));
})
.catch((e) => {
reject(e as Error);
});
})
.catch((e) => {
reject(e as Error);
});
});
}
export async function parseProblem(problem: string) {

View File

@@ -7,20 +7,19 @@ import { browser } from '$app/environment';
const _ndk = new NDKSvelte({
explicitRelayUrls: [
'wss://purplepag.es',
'wss://relay.higlighter.com',
'wss://relay.nostr.band',
'wss://nos.lol',
'wss://relay.nostrocket.org',
'wss://nostr.mutinywallet.com',
'wss://relay.damus.io'
],
enableOutboxModel: false,
enableOutboxModel: false
//clientNip89: "31990:fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52:1716498133442",
});
//we need to check for browser environment before calling window because svelte is slightly retarded when used client side only
// if (browser && window.indexedDB) {
// _ndk.cacheAdapter = new NDKCacheAdapterDexie({ dbName: 'gulag' });
// }
if (browser && window.indexedDB) {
_ndk.cacheAdapter = new NDKCacheAdapterDexie({ dbName: 'gulag' });
}
export const ndk = writable(_ndk);

View File

@@ -0,0 +1 @@
Wouldn't it be cool to have a nostr help thread here?

View File

@@ -0,0 +1 @@
<img src="https://i.imgflip.com/908qyq.jpg" />

View File

@@ -0,0 +1,8 @@
<script lang="ts">
</script>
This is a work in progress, for now please have a look at the <a
class="underline"
href="https://nostrocket.github.io/oxygen/nr/Nostrocket/problems/d0afd68b5cafa58382edb38b7ac7feef229a916f22330922e4be6cd22193b1a5?tab=problem"
>Oxygen problem tracker</a
>.

View File

@@ -96,9 +96,7 @@
{/each}
{:else}
<Alert.Root>
<Alert.Description
>Currently, there are no products on the mainnet; you can check the testnet.</Alert.Description
>
<Alert.Description>No products found!</Alert.Description>
</Alert.Root>
{/if}
</Tabs.Content>
@@ -106,8 +104,7 @@
<Alert.Root class="my-2">
<ExclamationTriangle class="h-4 w-4" />
<Alert.Title>Note</Alert.Title>
<Alert.Description
>The following products are for testing purposes only. Please do not send real Bitcoin.</Alert.Description
<Alert.Description>The following products are for testing and are not real.</Alert.Description
>
</Alert.Root>
{#each testnet as [rocket, groups] (rocket.Event.id)}

View File

@@ -14,50 +14,62 @@
export let rockets: Readable<Rocket[]>;
let validAssociationRequests = derived([associations, rockets], ([$associationEvents, $rockets]) => {
let valid = new Map<string, BitcoinAssociation>();
for (let e of $associationEvents) {
let a = new BitcoinAssociation(e)
if (a.Validate()) {
valid.set(a.Event.id, a);
let validAssociationRequests = derived(
[associations, rockets],
([$associationEvents, $rockets]) => {
let valid = new Map<string, BitcoinAssociation>();
for (let e of $associationEvents) {
let a = new BitcoinAssociation(e);
if (a.Validate()) {
valid.set(a.Event.id, a);
}
}
return valid;
}
return valid
});
);
let validAgainstMyRocket = derived([currentUser, validAssociationRequests, rockets], ([$currentUser, $associationRequests, $rockets]) => {
let valid:{rocket:Rocket, association:BitcoinAssociation}[] = []
if ($currentUser) {
if ($rockets.length > 0) {
for (let [_, a] of $associationRequests) {
for (let r of $rockets) {
if (r.VotePowerForPubkey($currentUser.pubkey)) {
if (!r.BitcoinAssociations().get(a.Pubkey)) { //todo: get current list of Bitcoin associations, if (this is not included)
valid.push({rocket:r, association:a})
let validAgainstMyRocket = derived(
[currentUser, validAssociationRequests, rockets],
([$currentUser, $associationRequests, $rockets]) => {
let valid: { rocket: Rocket; association: BitcoinAssociation }[] = [];
if ($currentUser) {
if ($rockets.length > 0) {
for (let [_, a] of $associationRequests) {
for (let r of $rockets) {
if (r.VotePowerForPubkey($currentUser.pubkey)) {
if (!r.BitcoinAssociations().get(a.Pubkey)) {
//todo: get current list of Bitcoin associations, if (this is not included)
valid.push({ rocket: r, association: a });
}
}
}
}
}
}
return valid;
}
);
validAgainstMyRocket.subscribe((requests) => {
if ($rockets && $rockets.length > 0 && currentUser && $currentUser) {
for (let { rocket, association } of requests) {
if (
rocket.VotePowerForPubkey($currentUser.pubkey) &&
!rocket.Included(association.Event.id)
) {
for (let a of association.Event.getMatchingTags('a')) {
if (a.length == 2 && a[1] == rocket.ATag()[1]) {
let e = rocket.UpsertBitcoinAssociation(association);
if (e) {
e.ndk = $ndk;
e.publish().then((x) => {
console.log(x, e);
});
}
}
}
}
}
}
}
return valid;
})
validAgainstMyRocket.subscribe((requests) => {
if ($rockets && $rockets.length > 0 && currentUser && $currentUser) {
for (let {rocket,association} of requests) {
if (rocket.VotePowerForPubkey($currentUser.pubkey)) {
console.log("todo: check which rocket user has specified")
let e = rocket.UpsertBitcoinAssociation(association)
if (e) {
e.ndk = $ndk
e.publish().then(x=>{
console.log(x, e)
})
}
}
}
}
});
</script>

View File

@@ -1,4 +1,5 @@
<script lang="ts">
import { goto } from '$app/navigation';
import { base } from '$app/paths';
import { Button } from '$lib/components/ui/button/index.js';
import * as Card from '$lib/components/ui/card/index.js';
@@ -25,7 +26,7 @@
<Card.Root class="flex h-full w-full flex-col">
<Card.Header>
<Card.Title>Proof of Work</Card.Title>
<Card.Description>I have skills and I'm ready to work for sats.</Card.Description>
<Card.Description>I want to work for sats</Card.Description>
</Card.Header>
<img src="{base}/_work.png" alt="Proof of Work" />
<Card.Content class="grid gap-4 !pt-2">
@@ -33,38 +34,45 @@
<ul class="ml-6 list-disc [&>li]:mt-2">
<li>Solve problems for fun and profit</li>
<li>No meetings, no scrum, no task assignment, no schedules</li>
<li>Work only on what inspires you</li>
<li>Only work on fully open source projects</li>
<li>
No interview process or CV's required, apply by sending a pull request to solve a
small problem
No interview process! Apply by choosing a problem to solve and sending a pull request
</li>
<li>Get paid in Sats for every pull request</li>
<li>Get paid in Sats, or equity in the project's satflow</li>
</ul>
</div>
</Card.Content>
<Card.Footer class="mt-auto">
<Button class="w-full">Apply Now</Button>
<Button
on:click={() => {
window.location.href = 'https://github.com/nostrocket/hypergolic/issues';
}}
class="w-full">Apply Now</Button
>
</Card.Footer>
</Card.Root>
<Card.Root class="flex h-full w-full flex-col">
<Card.Header>
<Card.Title>Proof of Sats</Card.Title>
<Card.Description>I have Sats, and I want a slice of a Rocket's satflow.</Card.Description>
<Card.Description>I want to invest in the satflow of a Rocket</Card.Description>
</Card.Header>
<img src="{base}/_sats.png" width="100%" alt="Proof of Sats" />
<Card.Content class="grid gap-4 !pt-2">
<div>
<ul class="ml-6 list-disc [&>li]:mt-2">
<li>Nostrocket is the most efficient way (ever) to invest in emerging technologies</li>
<li>
Every Sat goes directly to a contributor for solving a problem in the critical path
towards (more) revenue
Nostrocket is the most efficient way (ever) to invest in emerging technologies like
nostr
</li>
<li>
Revenue goes straight from the paying customer's wallet into your wallet as an equity
holder
Your sats go directly to a contributor for work that has already been completed and
solves a problem in the critical path to (more) revenue
</li>
<li>No KYC, ever.</li>
<li>
Nostrocket is non-custodial - revenue goes directly from the paying customer to your
wallet as an equity holder
</li>
<li>No KYC (ever).</li>
</ul>
</div>
</Card.Content>
@@ -76,7 +84,7 @@
<Card.Header>
<Card.Title>Launch a new Rocket</Card.Title>
<Card.Description
>I'm working on my own project and I want other people to join me.</Card.Description
>I have an open source project and I want to make it profitable</Card.Description
>
</Card.Header>
<img src="{base}/_rocket.png" width="100%" alt="Launch a new Rocket" />
@@ -84,16 +92,16 @@
<div>
<ul class="ml-6 list-disc [&>li]:mt-2">
<li>
Attract the people you need to make your project a success (despite your lack of
funding)
Attract the right people to make your project a success (despite your lack of funding)
</li>
<li>No financial accounting, capital raising, compliance, business plans, etc.</li>
<li>
Make your project socially scalable by becoming radically fair and meritocratic to all
contributors and investors.
</li>
<li>
Nostrocket obsoletes the need for financial accounting, capital raising, compliance,
business plans, etc.
</li>
<li>
Nostrocket is radically meritocratic and fair to project creators, contributors, and
investors, for the entire life of the project
Make your project immune to parasites by giving them nowhere to hide and nothing to
leech from.
</li>
</ul>
</div>

View File

@@ -84,10 +84,7 @@
<Alert.Root class="my-2">
<ExclamationTriangle class="h-4 w-4" />
<Alert.Title>Note</Alert.Title>
<Alert.Description
>The following rocket is for testing purposes only. Any rocket with "test" in its name is
intended solely for testing.</Alert.Description
>
<Alert.Description>The following rockets are for testing purposes only.</Alert.Description>
</Alert.Root>
<div class="grid grid-cols-1 gap-2 md:grid-cols-2 2xl:grid-cols-3 3xl:grid-cols-4">
{#each testnet as rocket (`${rocket.Event.pubkey}${rocket.Name()}`)}