mirror of
https://github.com/aljazceru/hypergolic.git
synced 2025-12-18 14:04:21 +01:00
Merge branch 'MASTER' into fix-style
This commit is contained in:
38
README.md
38
README.md
@@ -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.
|
||||
|
||||
@@ -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)}
|
||||
|
||||
122
src/components/AssociatedBitcoinAddresses.svelte
Normal file
122
src/components/AssociatedBitcoinAddresses.svelte
Normal 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>
|
||||
@@ -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>
|
||||
@@ -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']}
|
||||
|
||||
@@ -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')}>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
1
src/routes/help/+page.svelte
Normal file
1
src/routes/help/+page.svelte
Normal file
@@ -0,0 +1 @@
|
||||
Wouldn't it be cool to have a nostr help thread here?
|
||||
1
src/routes/inbox/+page.svelte
Normal file
1
src/routes/inbox/+page.svelte
Normal file
@@ -0,0 +1 @@
|
||||
<img src="https://i.imgflip.com/908qyq.jpg" />
|
||||
8
src/routes/problems/+page.svelte
Normal file
8
src/routes/problems/+page.svelte
Normal 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
|
||||
>.
|
||||
@@ -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)}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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()}`)}
|
||||
|
||||
Reference in New Issue
Block a user