mirror of
https://github.com/aljazceru/hypergolic.git
synced 2025-12-20 23:04:24 +01:00
problem: can't list amrs for sale
This commit is contained in:
29
package-lock.json
generated
29
package-lock.json
generated
@@ -14,6 +14,7 @@
|
||||
"@nostr-dev-kit/ndk-svelte": "^2.2.15",
|
||||
"@nostr-dev-kit/ndk-svelte-components": "^2.2.16",
|
||||
"@sveltejs/adapter-static": "^3.0.1",
|
||||
"bitcoin-address-validation": "^2.2.3",
|
||||
"bits-ui": "^0.21.10",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk-sv": "^0.0.17",
|
||||
@@ -1676,6 +1677,19 @@
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"node_modules/base58-js": {
|
||||
"version": "1.0.5",
|
||||
"resolved": "https://registry.npmjs.org/base58-js/-/base58-js-1.0.5.tgz",
|
||||
"integrity": "sha512-LkkAPP8Zu+c0SVNRTRVDyMfKVORThX+rCViget00xdgLRrKkClCTz1T7cIrpr69ShwV5XJuuoZvMvJ43yURwkA==",
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/bech32": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz",
|
||||
"integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg=="
|
||||
},
|
||||
"node_modules/binary-extensions": {
|
||||
"version": "2.3.0",
|
||||
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz",
|
||||
@@ -1687,6 +1701,16 @@
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/bitcoin-address-validation": {
|
||||
"version": "2.2.3",
|
||||
"resolved": "https://registry.npmjs.org/bitcoin-address-validation/-/bitcoin-address-validation-2.2.3.tgz",
|
||||
"integrity": "sha512-1uGCGl26Ye8JG5qcExtFLQfuib6qEZWNDo1ZlLlwp/z7ygUFby3IxolgEfgMGaC+LG9csbVASLcH8fRLv7DIOg==",
|
||||
"dependencies": {
|
||||
"base58-js": "^1.0.0",
|
||||
"bech32": "^2.0.0",
|
||||
"sha256-uint8array": "^0.10.3"
|
||||
}
|
||||
},
|
||||
"node_modules/bits-ui": {
|
||||
"version": "0.21.10",
|
||||
"resolved": "https://registry.npmjs.org/bits-ui/-/bits-ui-0.21.10.tgz",
|
||||
@@ -4492,6 +4516,11 @@
|
||||
"resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.6.0.tgz",
|
||||
"integrity": "sha512-RVnVQxTXuerk653XfuliOxBP81Sf0+qfQE73LIYKcyMYHG94AuH0kgrQpRDuTZnSmjpysHmzxJXKNfa6PjFhyQ=="
|
||||
},
|
||||
"node_modules/sha256-uint8array": {
|
||||
"version": "0.10.7",
|
||||
"resolved": "https://registry.npmjs.org/sha256-uint8array/-/sha256-uint8array-0.10.7.tgz",
|
||||
"integrity": "sha512-1Q6JQU4tX9NqsDGodej6pkrUVQVNapLZnvkwIhddH/JqzBZF1fSaxSWNY6sziXBE8aEa2twtGkXUrwzGeZCMpQ=="
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
|
||||
@@ -44,6 +44,7 @@
|
||||
"@nostr-dev-kit/ndk-svelte": "^2.2.15",
|
||||
"@nostr-dev-kit/ndk-svelte-components": "^2.2.16",
|
||||
"@sveltejs/adapter-static": "^3.0.1",
|
||||
"bitcoin-address-validation": "^2.2.3",
|
||||
"bits-ui": "^0.21.10",
|
||||
"clsx": "^2.1.1",
|
||||
"cmdk-sv": "^0.0.17",
|
||||
|
||||
@@ -44,15 +44,15 @@
|
||||
>
|
||||
<Dialog.Content>
|
||||
<Dialog.Header>
|
||||
<Dialog.Title>Calculate the sats</Dialog.Title>
|
||||
<Dialog.Title>How much should you request?</Dialog.Title>
|
||||
<Dialog.Description
|
||||
>Calculate the sats of the request according to your hour rate.</Dialog.Description
|
||||
>Use this form to calculate how many Sats you should claim. This information will be public and permanent.</Dialog.Description
|
||||
>
|
||||
</Dialog.Header>
|
||||
<Label for="hourly_rate">Hour rate</Label>
|
||||
<Input bind:value={hourly_rate} id="hourly_rate" placeholder="? USD / hour" />
|
||||
<Label for="spent_minutes">Working hours</Label>
|
||||
<Input bind:value={spent_minutes} id="spent_minutes" placeholder="? minutes" />
|
||||
<Label for="hourly_rate">Your hourly rate in CuckLoserBucks</Label>
|
||||
<Input bind:value={hourly_rate} id="hourly_rate" placeholder="USD hourly rate" />
|
||||
<Label for="spent_minutes">Time spent solving this problem (minutes)</Label>
|
||||
<Input bind:value={spent_minutes} id="spent_minutes" placeholder="minutes" />
|
||||
{#if calcSats}
|
||||
<div>Result: {calcSats.toFixed(0)} sats</div>
|
||||
{/if}
|
||||
|
||||
@@ -5,6 +5,8 @@
|
||||
import { Avatar } from '@nostr-dev-kit/ndk-svelte-components';
|
||||
import { NDKNip07Signer } from '@nostr-dev-kit/ndk';
|
||||
import { onMount } from 'svelte';
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
|
||||
onMount(() => {
|
||||
if (localStorage.getItem('signed-in')) {
|
||||
@@ -48,6 +50,9 @@
|
||||
<DropdownMenu.Content align="end">
|
||||
<DropdownMenu.Label>My Account</DropdownMenu.Label>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item on:click={()=>{goto(`${base}/sellmerits`)}}>Sell Merits</DropdownMenu.Item>
|
||||
<DropdownMenu.Item>Buy Merits</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Item>Settings</DropdownMenu.Item>
|
||||
<DropdownMenu.Item>Support</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import { getRocketURL, parseProblem } from '@/helpers';
|
||||
import type { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
import MeritSummaryCard from './MeritSummaryCard.svelte';
|
||||
import { Rocket } from '@/event_helpers/rockets';
|
||||
|
||||
export let rocket: NDKEvent;
|
||||
export let merit: MeritRequest;
|
||||
@@ -37,6 +38,6 @@
|
||||
<main
|
||||
class="grid flex-1 items-start gap-4 p-4 sm:px-6 sm:py-0 md:gap-2 lg:grid-cols-3 xl:grid-cols-3"
|
||||
>
|
||||
<MeritSummaryCard {rocket} {merit} />
|
||||
<MeritSummaryCard parsedRocket={new Rocket(rocket)} {merit} />
|
||||
</main>
|
||||
</div>
|
||||
|
||||
@@ -12,17 +12,17 @@
|
||||
import { onDestroy } from 'svelte';
|
||||
import { derived } from 'svelte/store';
|
||||
|
||||
export let rocket: NDKEvent;
|
||||
let parsedRocket = new Rocket(rocket);
|
||||
//export let rocket: NDKEvent;
|
||||
export let parsedRocket:Rocket;// = new Rocket(rocket);
|
||||
|
||||
let _merits = $ndk.storeSubscribe(
|
||||
[{ '#a': [`31108:${rocket.author.pubkey}:${rocket.dTag}`], kinds: [1409 as NDKKind] }],
|
||||
[{ '#a': [`31108:${parsedRocket.Event.author.pubkey}:${parsedRocket.Name()}`], kinds: [1409 as NDKKind] }],
|
||||
{
|
||||
subId: `${rocket.dTag}_merits`
|
||||
subId: `${parsedRocket.Name()}_merits`
|
||||
}
|
||||
);
|
||||
|
||||
let _votes = $ndk.storeSubscribe({ '#a': [RocketATagFilter(rocket)], kinds: [1410 as NDKKind] });
|
||||
let _votes = $ndk.storeSubscribe({ '#a': [RocketATagFilter(parsedRocket.Event)], kinds: [1410 as NDKKind] });
|
||||
|
||||
onDestroy(() => {
|
||||
_merits?.unsubscribe();
|
||||
|
||||
@@ -21,15 +21,15 @@
|
||||
import MeritComment from './MeritComment.svelte';
|
||||
|
||||
export let merit: MeritRequest;
|
||||
export let rocket: NDKEvent;
|
||||
//export let rocket: NDKEvent;
|
||||
let cuckPrice: number | undefined = undefined;
|
||||
|
||||
let result: VoteDirection | undefined;
|
||||
|
||||
let parsedRocket = new Rocket(rocket);
|
||||
export let parsedRocket:Rocket;
|
||||
|
||||
let _votes = $ndk.storeSubscribe(
|
||||
{ '#a': [RocketATagFilter(rocket)], '#e': [merit.ID], kinds: [1410 as NDKKind] },
|
||||
{ '#a': [RocketATagFilter(parsedRocket.Event)], '#e': [merit.ID], kinds: [1410 as NDKKind] },
|
||||
{
|
||||
subId: merit.ID
|
||||
}
|
||||
@@ -80,7 +80,7 @@
|
||||
updatedRocket.ndk = $ndk;
|
||||
updatedRocket.sign().then(() => {
|
||||
updatedRocket.publish().then(() => {
|
||||
goto(`${base}/rockets/${getRocketURL(rocket)}`);
|
||||
goto(`${base}/rockets/${getRocketURL(parsedRocket.Event)}`);
|
||||
});
|
||||
});
|
||||
}
|
||||
@@ -170,7 +170,7 @@
|
||||
<Separator class="my-4" />
|
||||
<div class="font-semibold">Votes</div>
|
||||
{#if $votes.size == 0}<Alert
|
||||
><Info />Waiting for existing <span class="italic">{new Rocket(rocket).Name()}</span> Merit
|
||||
><Info />Waiting for existing <span class="italic">{parsedRocket.Name()}</span> Merit
|
||||
holders to vote</Alert
|
||||
>
|
||||
{/if}
|
||||
@@ -219,7 +219,7 @@
|
||||
{:else if result == 'blackball'}
|
||||
<span class="scroll-m-20 text-lg font-semibold tracking-tight md:text-xl">REJECTED</span>
|
||||
{:else if !result}
|
||||
<VoteOnMeritRequest {merit} rocket={new Rocket(rocket)} />
|
||||
<VoteOnMeritRequest {merit} rocket={parsedRocket} />
|
||||
{/if}
|
||||
</Card.Footer>
|
||||
</Card.Root>
|
||||
|
||||
43
src/components/MeritTrades.svelte
Normal file
43
src/components/MeritTrades.svelte
Normal file
@@ -0,0 +1,43 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import * as Breadcrumb from '$lib/components/ui/breadcrumb/index.js';
|
||||
import { Rocket } from '@/event_helpers/rockets';
|
||||
import { getRocketURL } from '@/helpers';
|
||||
import type { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
|
||||
export let rocket: NDKEvent;
|
||||
let parsedRocket = new Rocket(rocket)
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<div class="flex flex-col sm:gap-4">
|
||||
<header class="flex items-center">
|
||||
<Breadcrumb.Root class="flex">
|
||||
<Breadcrumb.List>
|
||||
<Breadcrumb.Item>
|
||||
<Breadcrumb.Link href={`${base}/rockets/${getRocketURL(rocket)}`}
|
||||
>{rocket.getMatchingTags('d')[0][1]}</Breadcrumb.Link
|
||||
>
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Separator />
|
||||
<Breadcrumb.Item>
|
||||
<Breadcrumb.Link href={`${base}/rockets/${getRocketURL(rocket)}`}
|
||||
>Merit Requests</Breadcrumb.Link
|
||||
>
|
||||
</Breadcrumb.Item>
|
||||
<Breadcrumb.Separator />
|
||||
<Breadcrumb.Item>
|
||||
<Breadcrumb.Page>
|
||||
Trade
|
||||
</Breadcrumb.Page>
|
||||
</Breadcrumb.Item>
|
||||
</Breadcrumb.List>
|
||||
</Breadcrumb.Root>
|
||||
</header>
|
||||
<main
|
||||
class="grid flex-1 items-start gap-4 p-4 sm:px-6 sm:py-0 md:gap-2 lg:grid-cols-3 xl:grid-cols-3"
|
||||
>
|
||||
Trade {parsedRocket.Name()} Merits
|
||||
</main>
|
||||
</div>
|
||||
@@ -11,6 +11,7 @@
|
||||
import ProposedProducts from './ProposedProducts.svelte';
|
||||
import Todo from './Todo.svelte';
|
||||
import UpdateMission from './UpdateMission.svelte';
|
||||
import { Rocket } from '@/event_helpers/rockets';
|
||||
|
||||
export let rocket: NDKEvent;
|
||||
|
||||
@@ -40,7 +41,7 @@
|
||||
|
||||
<ProposedProducts {rocket} />
|
||||
|
||||
<MeritRequests {rocket} />
|
||||
<MeritRequests parsedRocket={new Rocket(rocket)} />
|
||||
<Card.Root class="sm:col-span-3">
|
||||
<Card.Header class="pb-3">
|
||||
<Card.Title>Actions</Card.Title>
|
||||
|
||||
@@ -36,6 +36,7 @@ export class MeritRequest {
|
||||
}
|
||||
IncludedInRocketState(rocket: Rocket): boolean {
|
||||
let included = rocket.ApprovedMeritRequests();
|
||||
console.log(39, rocket, included.get(this.ID))
|
||||
return Boolean(included.get(this.ID));
|
||||
}
|
||||
BasicValidation(): boolean {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { NDKEvent, type NDKTag } from '@nostr-dev-kit/ndk';
|
||||
import { MapOfVotes, MeritRequest, Votes } from './merits';
|
||||
import { getAuthorizedZapper } from '@/helpers';
|
||||
import validate from 'bitcoin-address-validation';
|
||||
|
||||
export class Rocket {
|
||||
Event: NDKEvent;
|
||||
@@ -37,12 +38,12 @@ export class Rocket {
|
||||
return amr;
|
||||
}
|
||||
TotalMerits(): number {
|
||||
let total = 0
|
||||
let amr = this.ApprovedMeritRequests()
|
||||
let total = 0;
|
||||
let amr = this.ApprovedMeritRequests();
|
||||
for (let [_, _amr] of amr) {
|
||||
total += _amr.Merits
|
||||
total += _amr.Merits;
|
||||
}
|
||||
return total
|
||||
return total;
|
||||
}
|
||||
ValidateAMRProof(amrProof: NDKEvent): boolean {
|
||||
let result = false;
|
||||
@@ -114,7 +115,19 @@ export class Rocket {
|
||||
event = new NDKEvent(this.Event.ndk, this.Event.rawEvent());
|
||||
event.created_at = Math.floor(new Date().getTime() / 1000);
|
||||
event.tags.push(['merit', `${request.Pubkey}:${request.ID}:0:0:${request.Merits}`]);
|
||||
event.tags.push(['proof_full', JSON.stringify(signedProof)]);
|
||||
event.tags.push(['proof_full', JSON.stringify(signedProof.rawEvent())]);
|
||||
updateIgnitionAndParentTag(event);
|
||||
}
|
||||
return event;
|
||||
}
|
||||
UpsertAMRAuction(request: AMRAuction): NDKEvent | undefined {
|
||||
let event: NDKEvent | undefined = undefined;
|
||||
if (request.ValidateAgainstRocket(this)) {
|
||||
this.PrepareForUpdate();
|
||||
event = new NDKEvent(this.Event.ndk, this.Event.rawEvent());
|
||||
event.created_at = Math.floor(new Date().getTime() / 1000);
|
||||
|
||||
event.tags.push(['proof_full', JSON.stringify(request.Event!.rawEvent())]);
|
||||
updateIgnitionAndParentTag(event);
|
||||
}
|
||||
return event;
|
||||
@@ -238,6 +251,7 @@ export class RocketAMR {
|
||||
LeadTime: number;
|
||||
LeadTimeUpdate: number;
|
||||
Merits: number;
|
||||
Extra: {};
|
||||
SatsOwed(): number {
|
||||
return 0;
|
||||
}
|
||||
@@ -436,3 +450,126 @@ export async function ValidateZapPublisher(rocket: NDKEvent, zap: NDKEvent): Pro
|
||||
// }).catch(()=>{reject(false)})
|
||||
});
|
||||
}
|
||||
|
||||
export class AMRAuction {
|
||||
AMRIDs: string[];
|
||||
Owner: string|undefined;
|
||||
StartPrice: number;
|
||||
EndPrice: number;
|
||||
RxAddress: string;
|
||||
RocketD: string;
|
||||
RocketP: string;
|
||||
Merits: number;
|
||||
Event: NDKEvent;
|
||||
GenerateEvent(): NDKEvent {
|
||||
let e = new NDKEvent();
|
||||
e.kind = 1412;
|
||||
e.created_at = Math.floor(new Date().getTime() / 1000);
|
||||
for (let id of this.AMRIDs) {
|
||||
e.tags.push(["request", id])
|
||||
}
|
||||
e.tags.push(['a', `31108:${this.RocketP}:${this.RocketD}`]);
|
||||
//todo: allow user to set start and end auction price
|
||||
|
||||
e.tags.push(['price', this.StartPrice + ":" + this.EndPrice]);
|
||||
e.tags.push(["onchain", this.RxAddress])
|
||||
return e
|
||||
}
|
||||
Push(amr: RocketAMR) {
|
||||
if (this.Owner && amr.Pubkey != this.Owner) {
|
||||
throw new Error("invalid pubkey")
|
||||
}
|
||||
this.Owner = amr.Pubkey
|
||||
this.AMRIDs.push(amr.ID);
|
||||
this.StartPrice += amr.Merits;
|
||||
this.EndPrice += amr.Merits;
|
||||
this.Merits += amr.Merits;
|
||||
}
|
||||
Pop(amr: RocketAMR) {
|
||||
if (this.AMRIDs.includes(amr.ID) && amr.Pubkey == this.Owner) {
|
||||
let n:string[] = []
|
||||
for (let id of this.AMRIDs) {
|
||||
if (id != amr.ID) {
|
||||
n.push(id)
|
||||
}
|
||||
}
|
||||
this.AMRIDs = n;
|
||||
//todo: allow user to set start/end price
|
||||
this.StartPrice -= amr.Merits
|
||||
this.EndPrice -= amr.Merits
|
||||
this.Merits -= amr.Merits
|
||||
}
|
||||
}
|
||||
Validate(): boolean {
|
||||
let valid = true;
|
||||
if (
|
||||
this.Owner?.length != 64 ||
|
||||
!this.StartPrice ||
|
||||
!this.EndPrice ||
|
||||
!validate(this.RxAddress) ||
|
||||
this.RocketP.length != 64
|
||||
) {
|
||||
valid = false;
|
||||
}
|
||||
for (let id of this.AMRIDs) {
|
||||
if (id.length != 64) {
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
return valid;
|
||||
}
|
||||
ValidateAgainstRocket(rocket: Rocket): boolean {
|
||||
let valid = true;
|
||||
for (let id of this.AMRIDs) {
|
||||
let rocketAMR = rocket.ApprovedMeritRequests().get(id);
|
||||
if (!rocketAMR || (rocketAMR && rocketAMR.Pubkey != this.Owner) || rocketAMR.LeadTime > 0) {
|
||||
valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
//todo: check if this AMR is already listed for sale
|
||||
return valid;
|
||||
}
|
||||
constructor(rocket?: NDKEvent, event?: NDKEvent, address?: string) {
|
||||
this.AMRIDs = [];
|
||||
this.Merits = 0
|
||||
this.EndPrice = 0
|
||||
this.StartPrice = 0
|
||||
if (rocket && !event) {
|
||||
this.RxAddress = address?address:""
|
||||
this.RocketD = rocket.dTag!;
|
||||
this.RocketP = rocket.author.pubkey;
|
||||
}
|
||||
if (event && !rocket) {
|
||||
this.Event = event
|
||||
for (let id of event.getMatchingTags("request")) {
|
||||
if (id && id.length == 2 && id[1].length == 64) {
|
||||
this.AMRIDs.push(id[1])
|
||||
}
|
||||
}
|
||||
this.Owner = event.author.pubkey;
|
||||
let price = event.tagValue('price');
|
||||
if (price) {
|
||||
let _start = price.split(':')[0];
|
||||
let _end = price.split(':')[1];
|
||||
let start = parseInt(_start, 10);
|
||||
let end = parseInt(_end, 10);
|
||||
this.StartPrice = start;
|
||||
this.EndPrice = end;
|
||||
}
|
||||
let address = event.tagValue('onchain');
|
||||
if (address) {
|
||||
if (validate(address)) {
|
||||
this.RxAddress = address;
|
||||
}
|
||||
}
|
||||
let _rocket = event.tagValue('a');
|
||||
if (_rocket) {
|
||||
if (_rocket.split(':').length == 3) {
|
||||
this.RocketP = _rocket.split(':')[1];
|
||||
this.RocketD = _rocket.split(':')[2];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
54
src/route_helpers/Rocket.svelte
Normal file
54
src/route_helpers/Rocket.svelte
Normal file
@@ -0,0 +1,54 @@
|
||||
<script lang="ts">
|
||||
import { ndk } from '@/ndk';
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
import type { ExtendedBaseType, NDKEventStore } from '@nostr-dev-kit/ndk-svelte';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { derived, type Readable } from 'svelte/store';
|
||||
|
||||
export let dTag: string; // = $page.url.searchParams.get('d');
|
||||
export let pubkey: string; // = $page.url.searchParams.get('p');
|
||||
|
||||
|
||||
let rocketEvents: NDKEventStore<NDKEvent> | undefined;
|
||||
export let latestRocketEvent: Readable<ExtendedBaseType<NDKEvent> | undefined>;
|
||||
|
||||
let candidateProducts: Readable<ExtendedBaseType<NDKEvent>[]>;
|
||||
onDestroy(() => {
|
||||
rocketEvents?.unsubscribe();
|
||||
});
|
||||
|
||||
rocketEvents = $ndk.storeSubscribe(
|
||||
[
|
||||
{ '#d': [dTag], authors: [pubkey], kinds: [31108 as number] },
|
||||
{ '#a': [`31108:${pubkey}:${dTag}`] }
|
||||
],
|
||||
{ subId: dTag }
|
||||
);
|
||||
|
||||
$: {
|
||||
if (rocketEvents && !latestRocketEvent) {
|
||||
latestRocketEvent = derived(rocketEvents, ($events) => {
|
||||
let sorted = $events.filter((e) => {
|
||||
return e.kind == 31108;
|
||||
});
|
||||
sorted = sorted.toSorted((a, b) => {
|
||||
return a.created_at - b.created_at;
|
||||
});
|
||||
return sorted[0];
|
||||
});
|
||||
|
||||
if ($latestRocketEvent && !candidateProducts) {
|
||||
candidateProducts = derived(rocketEvents, ($events) => {
|
||||
return $events.filter((e) => {
|
||||
for (let p of $latestRocketEvent.getMatchingTags('product')) {
|
||||
if (p[1].includes(e.id)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return e.kind == 1908;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
166
src/routes/buymerits/+page.svelte
Normal file
166
src/routes/buymerits/+page.svelte
Normal file
@@ -0,0 +1,166 @@
|
||||
<script lang="ts">
|
||||
import { currentUser } from '@/stores/session';
|
||||
import Login from '../../components/Login.svelte';
|
||||
import { ndk } from '@/ndk';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { derived } from 'svelte/store';
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
import { type RocketAMR, AMRAuction, Rocket } from '@/event_helpers/rockets';
|
||||
import * as Table from '@/components/ui/table';
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import Button from '@/components/ui/button/button.svelte';
|
||||
import { Input } from '@/components/ui/input';
|
||||
let rocketEvents = $ndk.storeSubscribe([{ kinds: [31108 as number] }], { subId: 'all_rockets' });
|
||||
onDestroy(() => {
|
||||
rocketEvents?.unsubscribe();
|
||||
});
|
||||
|
||||
let rockets = derived(rocketEvents, ($rocketEvents) => {
|
||||
let m = new Map<string, NDKEvent>();
|
||||
for (let e of $rocketEvents) {
|
||||
let existing = m.get(e.pubkey + e.dTag);
|
||||
if (!existing) {
|
||||
existing = e;
|
||||
}
|
||||
if (e.created_at > existing) {
|
||||
existing = e;
|
||||
}
|
||||
m.set(e.pubkey + e.dTag, e);
|
||||
}
|
||||
return Array.from(m, ([_, e]) => e);
|
||||
});
|
||||
|
||||
let myMeritRequests = derived([currentUser, rockets], ([$currentUser, $rockets]) => {
|
||||
let merits = new Map<Rocket, RocketAMR[]>();
|
||||
if ($currentUser) {
|
||||
for (let r of $rockets) {
|
||||
let parsedRocket = new Rocket(r);
|
||||
let _merits: RocketAMR[] = [];
|
||||
for (let [_, amr] of parsedRocket.ApprovedMeritRequests()) {
|
||||
if (amr.Pubkey == $currentUser.pubkey) {
|
||||
_merits.push(amr);
|
||||
}
|
||||
}
|
||||
merits.set(parsedRocket, _merits);
|
||||
}
|
||||
}
|
||||
return merits;
|
||||
});
|
||||
|
||||
let selected = new Map<string, AMRAuction>();
|
||||
function toggleSelected(amr: RocketAMR, rocket: Rocket) {
|
||||
if (selected.has(amr.ID)) {
|
||||
selected.delete(amr.ID);
|
||||
} else {
|
||||
selected.set(amr.ID, new AMRAuction(amr, rocket.Event));
|
||||
}
|
||||
selected = selected;
|
||||
}
|
||||
|
||||
function getTotal(list:Map<string, AMRAuction>):number {
|
||||
let total = 0
|
||||
for (let [_, amr] of list) {
|
||||
total += amr.Merits
|
||||
}
|
||||
return total
|
||||
}
|
||||
|
||||
function getAllForRocket(rocket:Rocket, selected:Map<string, AMRAuction>):Map<string, AMRAuction> {
|
||||
let thisRocket = new Map<string, AMRAuction>()
|
||||
for (let [_, amr] of selected) {
|
||||
if (amr.RocketD == rocket.Name() && amr.RocketP == rocket.Event.author.pubkey) {
|
||||
thisRocket.set(amr.AMRID, amr)
|
||||
}
|
||||
}
|
||||
return thisRocket
|
||||
}
|
||||
|
||||
function publish(sales:Map<string, AMRAuction>, address: string, rocket:Rocket) {
|
||||
if (!$ndk.signer) {
|
||||
throw new Error('no ndk signer found');
|
||||
}
|
||||
let e = new NDKEvent($ndk);
|
||||
let author = $currentUser;
|
||||
if (!author) {
|
||||
throw new Error('no current user');
|
||||
}
|
||||
|
||||
|
||||
e.author = author;
|
||||
e.kind = 1412;
|
||||
e.created_at = Math.floor(new Date().getTime() / 1000);
|
||||
for (let [_, amr] of sales) {
|
||||
e.tags.push(["request", amr.AMRID])
|
||||
}
|
||||
|
||||
e.tags.push(['a', `31108:${rocket.Event.pubkey}:${rocket.Event.dTag}`]);
|
||||
//todo: allow user to set start and end auction price
|
||||
let total = getTotal(getAllForRocket(rocket, sales)).toString()
|
||||
e.tags.push(['price', total + ":" + total]);
|
||||
e.tags.push(["onchain", address])
|
||||
e.publish().then((x) => {
|
||||
console.log(x, e);
|
||||
|
||||
//goto(`${base}/rockets/${getRocketURL(e)}`);
|
||||
});
|
||||
}
|
||||
|
||||
let bitcoinAddress:string = ""
|
||||
</script>
|
||||
|
||||
<h1 class=" m-2 text-nowrap text-center text-xl">Trade your Merits for Sats</h1>
|
||||
|
||||
{#if $currentUser}
|
||||
{#each $myMeritRequests as [rocket, amr]}
|
||||
{#if amr.length > 0}
|
||||
<h1>ROCKET: {rocket.Name()}</h1>
|
||||
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.Head class="w-[100px]">Selected</Table.Head>
|
||||
<Table.Head class="w-[10px]">AMR</Table.Head>
|
||||
<Table.Head class="w-[10px]">Elegible</Table.Head>
|
||||
<Table.Head>Merits</Table.Head>
|
||||
<Table.Head class="text-right">Sats (approx)</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each amr as a, id (a.ID)}
|
||||
<Table.Row class={selected.has(a.ID) ? 'bg-orange-500 hover:bg-orange-500' : ''}>
|
||||
<Table.Cell
|
||||
><Checkbox
|
||||
id={a.ID}
|
||||
checked={selected.has(a.ID)}
|
||||
on:click={() => {
|
||||
toggleSelected(a, rocket);
|
||||
}}
|
||||
/></Table.Cell
|
||||
>
|
||||
<Table.Cell
|
||||
><span
|
||||
class="cursor-pointer font-medium underline"
|
||||
on:click={() => {
|
||||
goto(`${base}/rockets/merits/${a.ID}`);
|
||||
}}>{a.ID.substring(0, 6)}</span
|
||||
></Table.Cell
|
||||
>
|
||||
<Table.Cell>{a.LeadTime == 0}</Table.Cell>
|
||||
<Table.Cell>{a.Merits}</Table.Cell>
|
||||
<Table.Cell class="text-right">{a.Merits}</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
{#if selected.size > 0}
|
||||
<div class="m-2 flex">You are selling {getTotal(getAllForRocket(rocket, selected))} Merits</div>
|
||||
<div class="m-2 flex">
|
||||
<Input bind:value={bitcoinAddress} type="text" placeholder="Bitcoin Address for Payment" class="m-1 max-w-xs" />
|
||||
<Button on:click={()=>{publish(getAllForRocket(rocket, selected), bitcoinAddress, rocket)}} class="m-1">Sell Now</Button>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
{:else}<Login />{/if}
|
||||
@@ -1,70 +1,31 @@
|
||||
<script lang="ts">
|
||||
import { page } from '$app/stores';
|
||||
import { ndk } from '@/ndk';
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
import type { ExtendedBaseType, NDKEventStore } from '@nostr-dev-kit/ndk-svelte';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { derived, type Readable } from 'svelte/store';
|
||||
import type { ExtendedBaseType } from '@nostr-dev-kit/ndk-svelte';
|
||||
import { type Readable } from 'svelte/store';
|
||||
import Heading from '../../../components/Heading.svelte';
|
||||
import RocketDashboard from '../../../components/RocketDashboard.svelte';
|
||||
import Rocket from '../../../route_helpers/Rocket.svelte';
|
||||
//flow if we only have a d-tag: fetch all 31108's with this d-tag, sort by WoT, put Nostrocket Name Service one at the top. Dedupe same rocket (same state, shadows) from multiple users, just show them all as everyone agreeing.
|
||||
//second pass: fetch ignition event for each, rebuild current state and validate all proofs, compute votepower and display only the states with > 50%.
|
||||
|
||||
let rIgnitionOrActual = $page.params.ignition;
|
||||
let rName = $page.url.searchParams.get('d');
|
||||
let rPubkey = $page.url.searchParams.get('p');
|
||||
let dTag = $page.url.searchParams.get('d');
|
||||
let pubkey = $page.url.searchParams.get('p');
|
||||
|
||||
let rocketEvents: NDKEventStore<NDKEvent> | undefined;
|
||||
let latestRocketEvent: Readable<ExtendedBaseType<NDKEvent> | undefined>;
|
||||
|
||||
let candidateProducts: Readable<ExtendedBaseType<NDKEvent>[]>;
|
||||
onDestroy(() => {
|
||||
rocketEvents?.unsubscribe();
|
||||
});
|
||||
|
||||
//if we don't have a d/p tags we just render the event provided
|
||||
//todo: to find the latest by name alone we should use a new route and redirect to this page once we've got the relevant data
|
||||
if (!rName && !rPubkey && rIgnitionOrActual.length == 64) {
|
||||
if (!dTag && !pubkey && rIgnitionOrActual.length == 64) {
|
||||
//this is the actual event ID the user wants to view so fetch the single event and render
|
||||
//warn user that this information is probably out of date and let them reroute to get the latest
|
||||
}
|
||||
|
||||
if (rName && rPubkey) {
|
||||
if (dTag && pubkey) {
|
||||
//the user wants the latest valid state of this rocket
|
||||
rocketEvents = $ndk.storeSubscribe(
|
||||
[
|
||||
{ '#d': [rName], authors: [rPubkey], kinds: [31108 as number] },
|
||||
{ '#a': [`31108:${rPubkey}:${rName}`] }
|
||||
],
|
||||
{ subId: rName }
|
||||
);
|
||||
}
|
||||
|
||||
$: {
|
||||
if (rocketEvents && !latestRocketEvent) {
|
||||
latestRocketEvent = derived(rocketEvents, ($events) => {
|
||||
let sorted = $events.filter((e) => {
|
||||
return e.kind == 31108;
|
||||
});
|
||||
sorted = sorted.toSorted((a, b) => {
|
||||
return a.created_at - b.created_at;
|
||||
});
|
||||
return sorted[0];
|
||||
});
|
||||
|
||||
if ($latestRocketEvent && !candidateProducts) {
|
||||
candidateProducts = derived(rocketEvents, ($events) => {
|
||||
return $events.filter((e) => {
|
||||
for (let p of $latestRocketEvent.getMatchingTags('product')) {
|
||||
if (p[1].includes(e.id)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return e.kind == 1908;
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//todo: check that this zap is not already included in the payment JSON for the product
|
||||
@@ -75,12 +36,15 @@
|
||||
|
||||
//todo: handle shadow events (fetch the shadowed event and render it instead)
|
||||
</script>
|
||||
{#if dTag && pubkey}
|
||||
<Rocket bind:latestRocketEvent {dTag} {pubkey} />
|
||||
{/if}
|
||||
|
||||
{#if latestRocketEvent && $latestRocketEvent}
|
||||
<RocketDashboard rocket={$latestRocketEvent} />
|
||||
{:else}
|
||||
<Heading title="Fetching events for the requested rocket" />
|
||||
IGNITION: {rIgnitionOrActual} <br />
|
||||
NAME: {rName} <br />
|
||||
PUBKEY: {rPubkey} <br />
|
||||
NAME: {dTag} <br />
|
||||
PUBKEY: {pubkey} <br />
|
||||
{/if}
|
||||
186
src/routes/sellmerits/+page.svelte
Normal file
186
src/routes/sellmerits/+page.svelte
Normal file
@@ -0,0 +1,186 @@
|
||||
<script lang="ts">
|
||||
import { goto } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import Button from '@/components/ui/button/button.svelte';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import * as Table from '@/components/ui/table';
|
||||
import { type RocketAMR, AMRAuction, Rocket } from '@/event_helpers/rockets';
|
||||
import { ndk } from '@/ndk';
|
||||
import { currentUser } from '@/stores/session';
|
||||
import { NDKEvent } from '@nostr-dev-kit/ndk';
|
||||
import { onDestroy } from 'svelte';
|
||||
import { derived } from 'svelte/store';
|
||||
import Login from '../../components/Login.svelte';
|
||||
import MeritAuctions from '../../stateupdaters/MeritAuctions.svelte';
|
||||
let rocketEvents = $ndk.storeSubscribe([{ kinds: [31108 as number] }], { subId: 'all_rockets' });
|
||||
onDestroy(() => {
|
||||
rocketEvents?.unsubscribe();
|
||||
});
|
||||
|
||||
let rockets = derived(rocketEvents, ($rocketEvents) => {
|
||||
let m = new Map<string, NDKEvent>();
|
||||
for (let e of $rocketEvents) {
|
||||
let existing = m.get(e.pubkey + e.dTag);
|
||||
if (!existing) {
|
||||
existing = e;
|
||||
}
|
||||
if (e.created_at > existing) {
|
||||
existing = e;
|
||||
}
|
||||
m.set(e.pubkey + e.dTag, e);
|
||||
}
|
||||
return Array.from(m, ([_, e]) => e);
|
||||
});
|
||||
|
||||
let myMeritRequests = derived([currentUser, rockets], ([$currentUser, $rockets]) => {
|
||||
let merits = new Map<Rocket, RocketAMR[]>();
|
||||
if ($currentUser) {
|
||||
for (let r of $rockets) {
|
||||
let parsedRocket = new Rocket(r);
|
||||
let _merits: RocketAMR[] = [];
|
||||
for (let [_, amr] of parsedRocket.ApprovedMeritRequests()) {
|
||||
if (amr.Pubkey == $currentUser.pubkey) {
|
||||
_merits.push(amr);
|
||||
}
|
||||
}
|
||||
merits.set(parsedRocket, _merits);
|
||||
}
|
||||
}
|
||||
return merits;
|
||||
});
|
||||
|
||||
let selected_amrs = new Map<string, AMRAuction>();
|
||||
function toggleSelected(amr: RocketAMR, rocket: Rocket) {
|
||||
if (!selected_amrs.has(rocket.Event.id)) {
|
||||
selected_amrs.set(rocket.Event.id, new AMRAuction(rocket.Event))
|
||||
}
|
||||
let existing = selected_amrs.get(rocket.Event.id)!
|
||||
if (existing.AMRIDs.includes(amr.ID)) {
|
||||
existing.Pop(amr)
|
||||
} else {
|
||||
existing.Push(amr)
|
||||
}
|
||||
selected_amrs.set(rocket.Event.id, existing)
|
||||
selected_amrs = selected_amrs;
|
||||
}
|
||||
|
||||
function getSelectedStatus(rocket: string, id:string, data: Map<string, AMRAuction>):boolean {
|
||||
let has = false
|
||||
let amr = data.get(rocket);
|
||||
if (amr) {
|
||||
has = amr.AMRIDs.includes(id)
|
||||
}
|
||||
return has
|
||||
}
|
||||
|
||||
function getMerits(rocket:string, data:Map<string, AMRAuction>):number {
|
||||
let m = data.get(rocket)
|
||||
console.log(m)
|
||||
let merits = 0
|
||||
if (m && m.Merits) {
|
||||
merits = m.Merits
|
||||
}
|
||||
return merits
|
||||
}
|
||||
|
||||
|
||||
|
||||
// function getTotal(auction:AMRAuction):number {
|
||||
// let total = 0
|
||||
// for (let [_, amr] of list) {
|
||||
// total += amr.Merits
|
||||
// }
|
||||
// return total
|
||||
// }
|
||||
|
||||
// function getAllForRocket(rocket:Rocket, selected:Map<string, AMRAuction>):Map<string, AMRAuction> {
|
||||
// let thisRocket = new Map<string, AMRAuction>()
|
||||
// for (let [_, amr] of selected) {
|
||||
// if (amr.RocketD == rocket.Name() && amr.RocketP == rocket.Event.author.pubkey) {
|
||||
// thisRocket.set(amr.AMRID, amr)
|
||||
// }
|
||||
// }
|
||||
// return thisRocket
|
||||
// }
|
||||
|
||||
function publish(amr:AMRAuction, rocket:Rocket) {
|
||||
if (!$ndk.signer) {
|
||||
throw new Error('no ndk signer found');
|
||||
}
|
||||
|
||||
let author = $currentUser;
|
||||
if (!author) {
|
||||
throw new Error('no current user');
|
||||
}
|
||||
amr.RxAddress = bitcoinAddress
|
||||
if (!amr.Validate()) {throw new Error("invalid")}
|
||||
if (!amr.ValidateAgainstRocket(rocket)) {throw new Error("invalid against rocket")}
|
||||
let e = amr.GenerateEvent()
|
||||
e.ndk = $ndk
|
||||
e.author = author;
|
||||
e.publish().then((x) => {
|
||||
console.log(x, e);
|
||||
selected_amrs = new Map<string, AMRAuction>();
|
||||
//goto(`${base}/rockets/${getRocketURL(e)}`);
|
||||
});
|
||||
}
|
||||
|
||||
let bitcoinAddress:string = ""
|
||||
</script>
|
||||
|
||||
<h1 class=" m-2 text-nowrap text-center text-xl">Trade your Merits for Sats</h1>
|
||||
|
||||
{#if $currentUser}
|
||||
{#each $myMeritRequests as [rocket, amr]}
|
||||
{#if amr.length > 0}
|
||||
<h1>ROCKET: {rocket.Name()}</h1>
|
||||
|
||||
<Table.Root>
|
||||
<Table.Header>
|
||||
<Table.Row>
|
||||
<Table.Head class="w-[100px]">Selected</Table.Head>
|
||||
<Table.Head class="w-[10px]">AMR</Table.Head>
|
||||
<Table.Head class="w-[10px]">Elegible</Table.Head>
|
||||
<Table.Head>Merits</Table.Head>
|
||||
<Table.Head class="text-right">Sats (approx)</Table.Head>
|
||||
</Table.Row>
|
||||
</Table.Header>
|
||||
<Table.Body>
|
||||
{#each amr as a, id (a.ID)}
|
||||
<Table.Row class={getSelectedStatus(rocket.Event.id, a.ID, selected_amrs) ? 'bg-orange-500 hover:bg-orange-500' : ''}>
|
||||
<Table.Cell
|
||||
><Checkbox
|
||||
id={a.ID}
|
||||
checked={getSelectedStatus(rocket.Event.id, a.ID, selected_amrs)}
|
||||
on:click={() => {
|
||||
toggleSelected(a, rocket);
|
||||
}}
|
||||
/></Table.Cell
|
||||
>
|
||||
<Table.Cell
|
||||
><span
|
||||
class="cursor-pointer font-medium underline"
|
||||
on:click={() => {
|
||||
goto(`${base}/rockets/merits/${a.ID}`);
|
||||
}}>{a.ID.substring(0, 6)}</span
|
||||
></Table.Cell
|
||||
>
|
||||
<Table.Cell>{a.LeadTime == 0}</Table.Cell>
|
||||
<Table.Cell>{a.Merits}</Table.Cell>
|
||||
<Table.Cell class="text-right">{a.Merits}</Table.Cell>
|
||||
</Table.Row>
|
||||
{/each}
|
||||
</Table.Body>
|
||||
</Table.Root>
|
||||
{#if selected_amrs.get(rocket.Event.id)}
|
||||
<div class="m-2 flex">You are selling {selected_amrs.get(rocket.Event.id)?.Merits} Merits</div>
|
||||
<div class="m-2 flex">
|
||||
<Input bind:value={bitcoinAddress} type="text" placeholder="Bitcoin Address for Payment" class="m-1 max-w-xs" />
|
||||
<Button on:click={()=>{publish(selected_amrs.get(rocket.Event.id), rocket)}} class="m-1">Sell Now</Button>
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
{/each}
|
||||
{:else}<Login />{/if}
|
||||
<MeritAuctions />
|
||||
43
src/stateupdaters/MeritAuctions.svelte
Normal file
43
src/stateupdaters/MeritAuctions.svelte
Normal file
@@ -0,0 +1,43 @@
|
||||
<script lang="ts">
|
||||
import { AMRAuction, Rocket } from "@/event_helpers/rockets";
|
||||
import { ndk } from "@/ndk";
|
||||
import { onDestroy } from "svelte";
|
||||
import { derived } from "svelte/store";
|
||||
|
||||
let saleEvents = $ndk.storeSubscribe([{ kinds: [1412 as number] }], { subId: 'all_sale_requests' });
|
||||
onDestroy(() => {
|
||||
saleEvents?.unsubscribe();
|
||||
});
|
||||
|
||||
export let rockets:Set<Rocket>|undefined = undefined;
|
||||
|
||||
let validAuctionRequests = derived(saleEvents, ($saleEvents) =>{
|
||||
let provisional = new Map<string, AMRAuction>()
|
||||
let valid = new Map<string, AMRAuction>()
|
||||
for (let e of $saleEvents) {
|
||||
let a = new AMRAuction(undefined, e)
|
||||
|
||||
if (a.Validate()) {
|
||||
provisional.set(a.Event.id, a)
|
||||
}
|
||||
|
||||
}
|
||||
if (rockets) {
|
||||
for (let [_, a] of provisional) {
|
||||
for (let r of rockets) {
|
||||
if (r.Name() == a.RocketD && a.RocketP == r.Event.pubkey) {
|
||||
if (a.ValidateAgainstRocket(r)) {
|
||||
valid.set(a.Event.id, a)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {valid = provisional}
|
||||
return valid
|
||||
})
|
||||
validAuctionRequests.subscribe(requests=>{
|
||||
|
||||
})
|
||||
</script>
|
||||
|
||||
{#each $validAuctionRequests as [_, a]}<span on:click={()=>{console.log(a)}}>{a.Event.id}</span>{/each}
|
||||
Reference in New Issue
Block a user