From f81f175c1eec32843a251f09deee91137940f740 Mon Sep 17 00:00:00 2001 From: gsovereignty Date: Sat, 10 Aug 2024 13:48:58 +0800 Subject: [PATCH 1/7] problem: wording isn't good --- src/components/NotifyMe.svelte | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/components/NotifyMe.svelte b/src/components/NotifyMe.svelte index b474283..1c4ed73 100644 --- a/src/components/NotifyMe.svelte +++ b/src/components/NotifyMe.svelte @@ -84,18 +84,18 @@ variant="nostr" class="flex h-8 shrink-0 items-center justify-center rounded-sm" > - Tell me via DM when there are updates + Nostrocket is totally not ready yet but whatever Subscribe for Updates - Receive notifications about Nostrocket updates via Nostr DM or email. + Subscribe via DM or email and we'll ping you when there are new releases/features
{#if $currentUser} - + {:else} {/if} @@ -108,7 +108,9 @@
{emailError}
{/if}
- +
From bfbd88ea055b2873e17e4a8cd848af1b2e8b74b5 Mon Sep 17 00:00:00 2001 From: gsovereignty Date: Sat, 10 Aug 2024 17:46:35 +0800 Subject: [PATCH 2/7] problem: can't use multiple bitcoin addresses for a single pubkey --- src/components/BitcoinAssociations.svelte | 30 +++++++----- src/components/NotifyMe.svelte | 9 ++-- src/lib/event_helpers/rockets.ts | 57 ++++++++++++----------- 3 files changed, 55 insertions(+), 41 deletions(-) diff --git a/src/components/BitcoinAssociations.svelte b/src/components/BitcoinAssociations.svelte index 0650a85..b457444 100644 --- a/src/components/BitcoinAssociations.svelte +++ b/src/components/BitcoinAssociations.svelte @@ -21,17 +21,24 @@ _associationRequests?.unsubscribe(); }); - let addresses = new Map() + let addresses = new Map(); - onMount(()=>{ - addresses = rocket.BitcoinAssociations() - addresses.forEach(a => { + onMount(() => { + addresses = rocket.BitcoinAssociations(); + addresses.forEach((a) => { if (a.Address) { - getBalance(a.Address).then(v=>{a.Balance = v; addresses.set(a.Pubkey, a); addresses = addresses}).catch(err=>{console.log(err)}) + getBalance(a.Address) + .then((v) => { + a.Balance = v; + addresses.set(a.Address!, a); + addresses = addresses; + }) + .catch((err) => { + console.log(err); + }); } - }) - }) - + }); + }); @@ -51,18 +58,18 @@ - {#each addresses as [pubkey, ba], _ (pubkey)} + {#each addresses as [address, ba], _ (address)}
@@ -71,7 +78,6 @@ {ba.Balance.toLocaleString()}
{ba.Address} -
{/each}
diff --git a/src/components/NotifyMe.svelte b/src/components/NotifyMe.svelte index 1c4ed73..9243b79 100644 --- a/src/components/NotifyMe.svelte +++ b/src/components/NotifyMe.svelte @@ -91,15 +91,18 @@ Subscribe for Updates - Subscribe via DM or email and we'll ping you when there are new releases/features + Subscribe now and we'll ping you when there are new releases/features
{#if $currentUser} - + {:else} {/if} + If you don't use nostr, you can subscribe to updates with an email address instead
@@ -109,7 +112,7 @@ {/if}
Please email me with updates diff --git a/src/lib/event_helpers/rockets.ts b/src/lib/event_helpers/rockets.ts index 97d0279..b2ce740 100644 --- a/src/lib/event_helpers/rockets.ts +++ b/src/lib/event_helpers/rockets.ts @@ -5,17 +5,20 @@ import validate from 'bitcoin-address-validation'; import { BitcoinTipTag, bitcoinTip, txs } from '@/stores/bitcoin'; export class Rocket { - UpsertBitcoinAssociation(association: BitcoinAssociation): NDKEvent { + Event: NDKEvent; + UpsertBitcoinAssociation(association: BitcoinAssociation): NDKEvent | undefined { let event: NDKEvent | undefined = undefined; - if (true) { - //todo: check if exists - this.PrepareForUpdate(); - event = new NDKEvent(this.Event.ndk, this.Event.rawEvent()); - event.created_at = Math.floor(new Date().getTime() / 1000); - event.tags.push(['address', `${association.Pubkey}:${association.Address}`]); - event.tags.push(['proof_full', JSON.stringify(association.Event.rawEvent())]); - updateIgnitionAndParentTag(event); - updateBitcoinTip(event); + if (association.Validate()) { + let existing = this.BitcoinAssociations().get(association.Address!); + if ((existing && existing.Pubkey != association.Pubkey) || !existing) { + this.PrepareForUpdate(); + event = new NDKEvent(this.Event.ndk, this.Event.rawEvent()); + event.created_at = Math.floor(new Date().getTime() / 1000); + event.tags.push(['address', `${association.Pubkey}:${association.Address}`]); + event.tags.push(['proof_full', JSON.stringify(association.Event.rawEvent())]); + updateIgnitionAndParentTag(event); + updateBitcoinTip(event); + } } return event; } @@ -29,14 +32,17 @@ export class Rocket { ba.Address = split[1]; ba.Pubkey = split[0]; if (ba.Validate()) { - a.set(ba.Pubkey, ba); + a.set(ba.Address, ba); } } } } return a; } - Event: NDKEvent; + UpsertMeritTransfer(): NDKEvent | undefined { + let event: NDKEvent | undefined = undefined; + return event; + } URL(): string { let ignitionID = undefined; @@ -419,7 +425,7 @@ export class RocketAMR { LeadTime: number; LeadTimeUpdate: number; Merits: number; - Extra: {eventAMR: AMRAuction}; + Extra: { eventAMR: AMRAuction }; SatsOwed(): number { return 0; } @@ -619,7 +625,12 @@ export async function ValidateZapPublisher(rocket: NDKEvent, zap: NDKEvent): Pro }); } -type AMRAuctionStatus = 'PENDING' | 'OPEN' | 'TX DETECTED' | 'SOLD & PENDING RATIFICATION' | 'CHECKING MEMPOOL'; +type AMRAuctionStatus = + | 'PENDING' + | 'OPEN' + | 'TX DETECTED' + | 'SOLD & PENDING RATIFICATION' + | 'CHECKING MEMPOOL'; export class AMRAuction { AMRIDs: string[]; @@ -632,12 +643,8 @@ export class AMRAuction { Merits: number; Event: NDKEvent; Extra: { rocket: Rocket }; - Status( - rocket: Rocket, - bitcoinTip: number, - transactions?: txs - ): AMRAuctionStatus { - let status:AMRAuctionStatus = "PENDING" + Status(rocket: Rocket, bitcoinTip: number, transactions?: txs): AMRAuctionStatus { + let status: AMRAuctionStatus = 'PENDING'; if (transactions && transactions.Address != this.RxAddress) { throw new Error('invalid address'); } @@ -663,17 +670,14 @@ export class AMRAuction { pending.RxAddress == this.RxAddress && pending.AMRIDs[0] == this.AMRIDs[0] //todo: check whole array ) { - found = true - if (status == "CHECKING MEMPOOL") { - if ( - Math.floor(new Date().getTime() / 1000) < transactions.LastUpdate + 60000 - ) { + found = true; + if (status == 'CHECKING MEMPOOL') { + if (Math.floor(new Date().getTime() / 1000) < transactions.LastUpdate + 60000) { status = 'OPEN'; } } } } - } return status; } @@ -805,6 +809,7 @@ export class BitcoinAssociation { Event: NDKEvent; Balance: number; Validate(): boolean { + console.log(819, this); let valid = true; if (this.Pubkey.length != 64) { valid = false; From 91c4446c309aadb2157215800fc9bd4eded44bf5 Mon Sep 17 00:00:00 2001 From: gsovereignty Date: Sat, 10 Aug 2024 19:30:15 +0800 Subject: [PATCH 3/7] problem: can't see existing bitcoin address registrations --- src/components/AssociateBitcoinAddress.svelte | 88 +++++++++++++------ src/lib/event_helpers/rockets.ts | 13 ++- src/routes/buymerits/+page.svelte | 51 +++++++---- 3 files changed, 105 insertions(+), 47 deletions(-) diff --git a/src/components/AssociateBitcoinAddress.svelte b/src/components/AssociateBitcoinAddress.svelte index 2fadc1f..8af22e7 100644 --- a/src/components/AssociateBitcoinAddress.svelte +++ b/src/components/AssociateBitcoinAddress.svelte @@ -1,18 +1,32 @@ + - Associate Bitcoin Address + Your Bitcoin Addresses -
- You must associate at least one Bitcoin address with your npub before you can pay a Contributor. Merit purchases from this address will be associated with your pubkey. -
-
-
-
\ No newline at end of file +
+ Merit purchases must be conducted with a Bitcoin address that is associated with your pubkey, + otherwise you will not recieve the Merits upon payment. +
+ {#if $associatedAddresses.size == 0}You do not have any registered addresses{:else} + Your registered addresses: +
    + {#each $associatedAddresses as address}
  • {address}
  • {/each} +
+ {/if} + Add a new address now +
+ +
+ + diff --git a/src/lib/event_helpers/rockets.ts b/src/lib/event_helpers/rockets.ts index b2ce740..2d16da1 100644 --- a/src/lib/event_helpers/rockets.ts +++ b/src/lib/event_helpers/rockets.ts @@ -1,8 +1,8 @@ -import { NDKEvent, type NDKTag } from '@nostr-dev-kit/ndk'; -import { MapOfVotes, MeritRequest, Votes } from './merits'; import { getAuthorizedZapper } from '@/helpers'; +import { BitcoinTipTag, txs } from '@/stores/bitcoin'; +import { NDKEvent, type NDKTag } from '@nostr-dev-kit/ndk'; import validate from 'bitcoin-address-validation'; -import { BitcoinTipTag, bitcoinTip, txs } from '@/stores/bitcoin'; +import { MapOfVotes, MeritRequest, Votes } from './merits'; export class Rocket { Event: NDKEvent; @@ -41,6 +41,13 @@ export class Rocket { } UpsertMeritTransfer(): NDKEvent | undefined { let event: NDKEvent | undefined = undefined; + this.PrepareForUpdate(); + event = new NDKEvent(this.Event.ndk, this.Event.rawEvent()); + event.created_at = Math.floor(new Date().getTime() / 1000); + event.tags.push(['address', `${association.Pubkey}:${association.Address}`]); + event.tags.push(['proof_full', JSON.stringify(association.Event.rawEvent())]); + updateIgnitionAndParentTag(event); + updateBitcoinTip(event); return event; } diff --git a/src/routes/buymerits/+page.svelte b/src/routes/buymerits/+page.svelte index 1d9b6f4..d786d84 100644 --- a/src/routes/buymerits/+page.svelte +++ b/src/routes/buymerits/+page.svelte @@ -111,26 +111,45 @@ if (t) console.log(t); }); + let nostrocket = derived(rockets, ($rockets) => { + let rocket: Rocket | undefined = undefined; + for (let r of $rockets) { + if ( + r.Name() == 'NOSTROCKET' && + r.Event.pubkey == 'd91191e30e00444b942c0e82cad470b32af171764c2275bee0bd99377efd4075' + ) { + //we consume the current list of bitcoin addresses from Nostrocket as a service so that users don't need to add a new address for every rocket + //todo: make this dependent on votepower not my pubkey + //todo: also allow rockets to have their own list of addresses so they can be used without nostrocket + rocket = r; + } + } + return rocket; + }); + transactions.subscribe((t) => {}); - let noAssociatedBitcoinAddress = derived( - [currentUser, pendingSales], - ([$currentUser, $pendingSales]) => { - let show = false; - if ($currentUser) { - for (let [r, a] of $pendingSales) { - if (a.length > 0 && !r.BitcoinAssociations().get($currentUser.pubkey)) { - console.log($currentUser.pubkey, r.Name()); - show = true; - } - } - } - return show; - } - ); + // let noAssociatedBitcoinAddress = derived( + // [currentUser, pendingSales], + // ([$currentUser, $pendingSales]) => { + // let show = false; + // if ($currentUser) { + // for (let [r, a] of $pendingSales) { + // if (a.length > 0) { + // let show = true + // for (let [_, ba] of r.BitcoinAssociations()) { + + // } + // } + // } + // } + // return show; + // } + // ); -{#if $noAssociatedBitcoinAddress}{/if} +{#if $nostrocket} +{/if} {#if $currentUser} {#each $pendingSales as [rocket, amr] (rocket.Event.id)} From 1c482ace4747ef240625bda5b21fc02de254198c Mon Sep 17 00:00:00 2001 From: gsovereignty Date: Sun, 11 Aug 2024 16:34:03 +0800 Subject: [PATCH 4/7] problem: rocket is not updated with valid merit transaction --- package-lock.json | 123 +++++++++++- package.json | 3 + src/components/AssociateBitcoinAddress.svelte | 2 + src/lib/event_helpers/merits.ts | 2 +- src/lib/event_helpers/rockets.ts | 181 +++++++++++++----- src/routes/buymerits/+page.svelte | 32 +--- 6 files changed, 268 insertions(+), 75 deletions(-) diff --git a/package-lock.json b/package-lock.json index dcb22e4..5e1bd7a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@mempool/mempool.js": "^2.3.0", "@nostr-dev-kit/ndk": "^2.8.2", "@nostr-dev-kit/ndk-cache-dexie": "^2.4.2", + "@nostr-dev-kit/ndk-cache-nostr": "^0.1.0", "@nostr-dev-kit/ndk-svelte": "^2.2.15", "@nostr-dev-kit/ndk-svelte-components": "^2.2.16", "@sveltejs/adapter-static": "^3.0.1", @@ -21,6 +22,8 @@ "cmdk-sv": "^0.0.17", "embla-carousel-svelte": "^8.1.3", "formsnap": "^1.0.0", + "immutable": "^4.3.7", + "js-sha256": "^0.11.0", "lucide-svelte": "^0.383.0", "mode-watcher": "^0.3.0", "paneforge": "^0.0.4", @@ -797,6 +800,115 @@ } } }, + "node_modules/@nostr-dev-kit/ndk-cache-nostr": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk-cache-nostr/-/ndk-cache-nostr-0.1.0.tgz", + "integrity": "sha512-NqQ9RWp9z21Tyrs8GoGrdAjzbEyJktkCQt/+rx6AexR4Gb3StkmlBpAaNrKib3l7ePHPJQWKYPSdR0pWqvnGrw==", + "dependencies": { + "@nostr-dev-kit/ndk": "2.10.0", + "debug": "^4.3.4", + "typescript": "^5.4.4", + "websocket-polyfill": "^0.0.3" + } + }, + "node_modules/@nostr-dev-kit/ndk-cache-nostr/node_modules/@noble/ciphers": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-0.5.3.tgz", + "integrity": "sha512-B0+6IIHiqEs3BPMT0hcRmHvEj2QHOLu+uwt+tqDDeVd0oyVzh7BPrDcPjRnV1PV/5LaknXJJQvOuRGR0zQJz+w==", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nostr-dev-kit/ndk-cache-nostr/node_modules/@noble/hashes": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.1.tgz", + "integrity": "sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nostr-dev-kit/ndk-cache-nostr/node_modules/@nostr-dev-kit/ndk": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk/-/ndk-2.10.0.tgz", + "integrity": "sha512-TqCAAo6ylORraAXrzRkCGFN2xTMiFbdER8Y8CtUT0HwOpFG/Wn+PBNeDeDmqkl/6LaPdeyXmVwCWj2KcUjIwYA==", + "dependencies": { + "@noble/curves": "^1.4.0", + "@noble/hashes": "^1.3.1", + "@noble/secp256k1": "^2.0.0", + "@scure/base": "^1.1.1", + "debug": "^4.3.4", + "light-bolt11-decoder": "^3.0.0", + "node-fetch": "^3.3.1", + "nostr-tools": "^2.7.1", + "tseep": "^1.1.1", + "typescript-lru-cache": "^2.0.0", + "utf8-buffer": "^1.0.0", + "websocket-polyfill": "^0.0.3" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@nostr-dev-kit/ndk-cache-nostr/node_modules/@scure/base": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.1.tgz", + "integrity": "sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA==", + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ] + }, + "node_modules/@nostr-dev-kit/ndk-cache-nostr/node_modules/nostr-tools": { + "version": "2.7.2", + "resolved": "https://registry.npmjs.org/nostr-tools/-/nostr-tools-2.7.2.tgz", + "integrity": "sha512-Bq3Ug0SZFtgtL1+0wCnAe8AJtI7yx/00/a2nUug9SkhfOwlKS92Tef12iCK9FdwXw+oFZWMtRnSwcLayQso+xA==", + "dependencies": { + "@noble/ciphers": "^0.5.1", + "@noble/curves": "1.2.0", + "@noble/hashes": "1.3.1", + "@scure/base": "1.1.1", + "@scure/bip32": "1.3.1", + "@scure/bip39": "1.2.1" + }, + "optionalDependencies": { + "nostr-wasm": "v0.1.0" + }, + "peerDependencies": { + "typescript": ">=5.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@nostr-dev-kit/ndk-cache-nostr/node_modules/nostr-tools/node_modules/@noble/curves": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", + "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", + "dependencies": { + "@noble/hashes": "1.3.2" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@nostr-dev-kit/ndk-cache-nostr/node_modules/nostr-tools/node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", + "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@nostr-dev-kit/ndk-svelte": { "version": "2.2.15", "resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk-svelte/-/ndk-svelte-2.2.15.tgz", @@ -2963,6 +3075,11 @@ "url": "https://github.com/sponsors/typicode" } }, + "node_modules/immutable": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.7.tgz", + "integrity": "sha512-1hqclzwYwjRDFLjcFxOM5AYkkG0rpFPpr1RLPMEuGczoS7YA8gLhy8SWXYRAA/XwfEHpfo3cw5JGioS32fnMRw==" + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -3168,6 +3285,11 @@ "@sideway/pinpoint": "^2.0.0" } }, + "node_modules/js-sha256": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.11.0.tgz", + "integrity": "sha512-6xNlKayMZvds9h1Y1VWc0fQHQ82BxTXizWPEtEeGvmOUYpBRy4gbWroHLpzowe6xiQhHpelCQiE7HEdznyBL9Q==" + }, "node_modules/json-schema-to-ts": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.0.tgz", @@ -5423,7 +5545,6 @@ "version": "5.4.5", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.5.tgz", "integrity": "sha512-vcI4UpRgg81oIRUFwR0WSIHKt11nJ7SAVlYNIu+QpqeyXP+gpQJy/Z4+F0aGxSE4MqwjyXvW/TzgkLAx2AGHwQ==", - "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" diff --git a/package.json b/package.json index 3350cf0..74c42f2 100644 --- a/package.json +++ b/package.json @@ -42,6 +42,7 @@ "@mempool/mempool.js": "^2.3.0", "@nostr-dev-kit/ndk": "^2.8.2", "@nostr-dev-kit/ndk-cache-dexie": "^2.4.2", + "@nostr-dev-kit/ndk-cache-nostr": "^0.1.0", "@nostr-dev-kit/ndk-svelte": "^2.2.15", "@nostr-dev-kit/ndk-svelte-components": "^2.2.16", "@sveltejs/adapter-static": "^3.0.1", @@ -51,6 +52,8 @@ "cmdk-sv": "^0.0.17", "embla-carousel-svelte": "^8.1.3", "formsnap": "^1.0.0", + "immutable": "^4.3.7", + "js-sha256": "^0.11.0", "lucide-svelte": "^0.383.0", "mode-watcher": "^0.3.0", "paneforge": "^0.0.4", diff --git a/src/components/AssociateBitcoinAddress.svelte b/src/components/AssociateBitcoinAddress.svelte index 8af22e7..5e48ce1 100644 --- a/src/components/AssociateBitcoinAddress.svelte +++ b/src/components/AssociateBitcoinAddress.svelte @@ -54,6 +54,8 @@ +Contributors who need Sats are able to list their Merits for sale, to sponsor them simply buy some of +their Merits. Your Bitcoin Addresses diff --git a/src/lib/event_helpers/merits.ts b/src/lib/event_helpers/merits.ts index 8ae6dcb..8f1266d 100644 --- a/src/lib/event_helpers/merits.ts +++ b/src/lib/event_helpers/merits.ts @@ -69,7 +69,7 @@ export class MeritRequest { } constructor(request: NDKEvent | string) { if (typeof request == 'string') { - console.log(69); + throw new Error('implement me'); } else { this.LeadTime = 0; this.LastLTUpdate = 0; diff --git a/src/lib/event_helpers/rockets.ts b/src/lib/event_helpers/rockets.ts index 2d16da1..074b6d1 100644 --- a/src/lib/event_helpers/rockets.ts +++ b/src/lib/event_helpers/rockets.ts @@ -2,7 +2,9 @@ import { getAuthorizedZapper } from '@/helpers'; import { BitcoinTipTag, txs } from '@/stores/bitcoin'; import { NDKEvent, type NDKTag } from '@nostr-dev-kit/ndk'; import validate from 'bitcoin-address-validation'; +import { sha256 } from 'js-sha256'; import { MapOfVotes, MeritRequest, Votes } from './merits'; +import * as immutable from 'immutable'; export class Rocket { Event: NDKEvent; @@ -39,15 +41,57 @@ export class Rocket { } return a; } - UpsertMeritTransfer(): NDKEvent | undefined { + UpsertLeadTime(event: NDKEvent): NDKEvent { + //todo: validate that there are no current auctions that include this AMR + return new NDKEvent(); + } + UpsertMeritTransfer(request: MeritPurchase): NDKEvent | undefined { let event: NDKEvent | undefined = undefined; - this.PrepareForUpdate(); - event = new NDKEvent(this.Event.ndk, this.Event.rawEvent()); - event.created_at = Math.floor(new Date().getTime() / 1000); - event.tags.push(['address', `${association.Pubkey}:${association.Address}`]); - event.tags.push(['proof_full', JSON.stringify(association.Event.rawEvent())]); - updateIgnitionAndParentTag(event); - updateBitcoinTip(event); + let fatal = false; + if (this.PendingAMRAuctionsMap().get(request.auction.ID())) { + this.PrepareForUpdate(); + let _event = new NDKEvent(this.Event.ndk, this.Event.rawEvent()); + _event.created_at = Math.floor(new Date().getTime() / 1000); + //delete the auction + let auctionID = request.auction.ID(); + let existing = _event.getMatchingTags('amr_auction'); + _event.removeTag('amr_auction'); + for (let t of existing) { + let amr = AMRAuctionFromTag(t, this.Event); + if (amr.ID() != auctionID) { + _event.tags.push(t); + } + } + _event.tags.push(['proof_raw', `txid:${request.txid}`]); + + let modifiedMerits: Map = new Map(); + for (let id of request.auction.AMRIDs) { + let amr = this.ApprovedMeritRequests().get(id); + if (!amr) { + return event; + } + if (amr.LeadTime > 0) { + return event; + } + amr.Pubkey = request.buyer; + modifiedMerits.set(amr.ID, amr); + } + let existingMerits = this.ApprovedMeritRequests(); + for (let [id, m] of modifiedMerits) { + existingMerits.set(id, m); + } + _event.removeTag('merit'); + for (let [id, m] of existingMerits) { + _event.tags.push(m.Tag()); + } + _event.tags.push([ + 'swap', + `${request.auction.Merits}:${request.sats}:${Math.floor(new Date().getTime() / 1000)}` + ]); + updateIgnitionAndParentTag(_event); + updateBitcoinTip(_event); + event = _event; + } return event; } @@ -194,47 +238,38 @@ export class Rocket { } return event; } - PendingAMRAuctions(): AMRAuction[] { - let auctions: AMRAuction[] = []; + PendingAMRAuctionsMap(): Map { + let m = new Map(); for (let t of this.Event.getMatchingTags('amr_auction')) { - if (t.length == 2) { - let items = t[1].split(':'); - if (items.length == 6) { - let a = new AMRAuction(this.Event); - a.RxAddress = items[0]; - a.StartPrice = parseInt(items[2], 10); - a.EndPrice = parseInt(items[3], 10); - a.Merits = parseInt(items[4], 10); - - let ids = items[5].match(/.{1,64}/g); - if (ids) { - for (let id of ids) { - a.AMRIDs.push(id); - } - } - let amrs = this.ApprovedMeritRequests(); - let failed = false; - for (let id of a.AMRIDs) { - let amr = amrs.get(id); - if (!amr) { - failed = true; - } else { - if (!a.Owner) { - a.Owner = amr.Pubkey; - } else if (a.Owner != amr.Pubkey) { - failed = true; - } - } - } - if (!failed) { - auctions.push(a); + let auction = AMRAuctionFromTag(t, this.Event); + if (auction.Validate()) { + let amrs = this.ApprovedMeritRequests(); + let failed = false; + for (let id of auction.AMRIDs) { + let amr = amrs.get(id); + if (!amr) { + failed = true; } else { - throw new Error('this should not happen, bug!'); + if (!auction.Owner) { + auction.Owner = amr.Pubkey; + } else if (auction.Owner != amr.Pubkey) { + failed = true; + } } } + if (!failed) { + m.set(auction.ID(), auction); + } else { + throw new Error('this should not happen, bug!'); + } } } - return auctions; + return m; + } + PendingAMRAuctions(): AMRAuction[] { + return Array.from(this.PendingAMRAuctionsMap(), ([_, amr]) => { + return amr; + }); } CanThisAMRBeSold(amr: string): boolean { let valid = true; @@ -433,7 +468,14 @@ export class RocketAMR { LeadTimeUpdate: number; Merits: number; Extra: { eventAMR: AMRAuction }; + Tag(): NDKTag { + return [ + 'merit', + `${this.Pubkey}:${this.ID}:${this.LeadTime}:${this.LeadTimeUpdate}:${this.Merits}` + ]; + } SatsOwed(): number { + //if rocket creator is acting as custodian instead of using a cashu mint return 0; } SatsPaid(): number { @@ -648,8 +690,12 @@ export class AMRAuction { RocketD: string; RocketP: string; Merits: number; - Event: NDKEvent; + Event: NDKEvent | undefined; Extra: { rocket: Rocket }; + ID(): string { + this.AMRIDs.sort(); + return sha256(''.concat(...this.AMRIDs).trim()); + } Status(rocket: Rocket, bitcoinTip: number, transactions?: txs): AMRAuctionStatus { let status: AMRAuctionStatus = 'PENDING'; if (transactions && transactions.Address != this.RxAddress) { @@ -731,12 +777,14 @@ export class AMRAuction { Validate(): boolean { let valid = true; if ( - this.Owner?.length != 64 || + (this.Owner && this.Owner.length != 64) || !this.StartPrice || !this.EndPrice || !validate(this.RxAddress) || - this.RocketP.length != 64 + this.RocketP.length != 64 || + !this.Merits ) { + //console.log(780, this, (this.Owner && this.Owner.length != 64), !this.StartPrice, !this.EndPrice, !validate(this.RxAddress), this.RocketP.length != 64, !this.Merits) valid = false; } for (let id of this.AMRIDs) { @@ -816,7 +864,6 @@ export class BitcoinAssociation { Event: NDKEvent; Balance: number; Validate(): boolean { - console.log(819, this); let valid = true; if (this.Pubkey.length != 64) { valid = false; @@ -903,3 +950,43 @@ export class Product { this.Event = event; } } + +export class MeritPurchase { + auction: AMRAuction; + buyer: string; + txid: string; + sats: number; + rocket: Rocket; + Validate(): boolean { + //todo: at least validate the utxo format + return true; + } + constructor(rocket: Rocket, auction: AMRAuction, buyer: string, txid: string, sats: number) { + this.rocket = rocket; + this.auction = auction; + this.buyer = buyer; + this.txid = txid; + this.sats = sats; + } +} + +function AMRAuctionFromTag(t: NDKTag, rocket: NDKEvent): AMRAuction { + let a = new AMRAuction(rocket); + if (t.length == 2) { + let items = t[1].split(':'); + if (items.length == 6) { + a.RxAddress = items[0]; + a.StartPrice = parseInt(items[2], 10); + a.EndPrice = parseInt(items[3], 10); + a.Merits = parseInt(items[4], 10); + + let ids = items[5].match(/.{1,64}/g); + if (ids) { + for (let id of ids) { + a.AMRIDs.push(id); + } + } + } + } + return a; +} diff --git a/src/routes/buymerits/+page.svelte b/src/routes/buymerits/+page.svelte index d786d84..ea33790 100644 --- a/src/routes/buymerits/+page.svelte +++ b/src/routes/buymerits/+page.svelte @@ -1,7 +1,7 @@ {#if $nostrocket} From 47be660c5223c8ec275727e559c22306014251e8 Mon Sep 17 00:00:00 2001 From: gsovereignty Date: Sun, 11 Aug 2024 18:16:01 +0800 Subject: [PATCH 5/7] problem: can't get change address --- src/lib/event_helpers/rockets.ts | 39 ++++++++++++++++++++++++------- src/lib/stores/bitcoin.ts | 10 +++++++- src/routes/buymerits/+page.svelte | 4 ++-- 3 files changed, 41 insertions(+), 12 deletions(-) diff --git a/src/lib/event_helpers/rockets.ts b/src/lib/event_helpers/rockets.ts index 074b6d1..9497096 100644 --- a/src/lib/event_helpers/rockets.ts +++ b/src/lib/event_helpers/rockets.ts @@ -1,5 +1,5 @@ import { getAuthorizedZapper } from '@/helpers'; -import { BitcoinTipTag, txs } from '@/stores/bitcoin'; +import { BitcoinTipTag, txo, txs } from '@/stores/bitcoin'; import { NDKEvent, type NDKTag } from '@nostr-dev-kit/ndk'; import validate from 'bitcoin-address-validation'; import { sha256 } from 'js-sha256'; @@ -47,7 +47,6 @@ export class Rocket { } UpsertMeritTransfer(request: MeritPurchase): NDKEvent | undefined { let event: NDKEvent | undefined = undefined; - let fatal = false; if (this.PendingAMRAuctionsMap().get(request.auction.ID())) { this.PrepareForUpdate(); let _event = new NDKEvent(this.Event.ndk, this.Event.rawEvent()); @@ -62,7 +61,7 @@ export class Rocket { _event.tags.push(t); } } - _event.tags.push(['proof_raw', `txid:${request.txid}`]); + _event.tags.push(['proof_raw', `txid:${request.tx.ID}`]); let modifiedMerits: Map = new Map(); for (let id of request.auction.AMRIDs) { @@ -86,8 +85,29 @@ export class Rocket { } _event.tags.push([ 'swap', - `${request.auction.Merits}:${request.sats}:${Math.floor(new Date().getTime() / 1000)}` + `${request.auction.Merits}:${request.tx.Amount}:${Math.floor(new Date().getTime() / 1000)}` ]); + let existingAssociation = this.BitcoinAssociations().get(request.tx.From); + if ( + !existingAssociation || + (existingAssociation && existingAssociation.Pubkey != request.buyer) + ) { + return event; + } + if (request.tx.Change) { + let existingAssociations = this.BitcoinAssociations(); + _event.removeTag('address'); + for (let [_, ba] of existingAssociations) { + if (ba.Address != request.tx.From) { + _event.tags.push(ba.Tag()); + } + if (ba.Address == request.tx.From) { + ba.Address = request.tx.Change; + _event.tags.push(ba.Tag()); + } + } + } + updateIgnitionAndParentTag(_event); updateBitcoinTip(_event); event = _event; @@ -863,6 +883,9 @@ export class BitcoinAssociation { Address: string | undefined; Event: NDKEvent; Balance: number; + Tag(): NDKTag { + return ['address', `${this.Pubkey}:${this.Address}`]; + } Validate(): boolean { let valid = true; if (this.Pubkey.length != 64) { @@ -954,19 +977,17 @@ export class Product { export class MeritPurchase { auction: AMRAuction; buyer: string; - txid: string; - sats: number; + tx: txo; rocket: Rocket; Validate(): boolean { //todo: at least validate the utxo format return true; } - constructor(rocket: Rocket, auction: AMRAuction, buyer: string, txid: string, sats: number) { + constructor(rocket: Rocket, auction: AMRAuction, buyer: string, tx: txo) { this.rocket = rocket; this.auction = auction; this.buyer = buyer; - this.txid = txid; - this.sats = sats; + this.tx = tx; } } diff --git a/src/lib/stores/bitcoin.ts b/src/lib/stores/bitcoin.ts index 3746612..69008bb 100644 --- a/src/lib/stores/bitcoin.ts +++ b/src/lib/stores/bitcoin.ts @@ -134,6 +134,7 @@ export class txs { let amount = 0; let height = tx.status.block_height ? tx.status.block_height : 0; let txid = tx.txid; + let change: string[] = []; for (let vout of tx.vout) { let address = vout.scriptpubkey_address; if (address && address.trim() == this.Address) { @@ -141,8 +142,11 @@ export class txs { if (value) { amount += parseInt(value, 10); } + } else { + change.push(address); } } + for (let vin of tx.vin) { let address = vin.prevout.scriptpubkey_address; if (address && validate(address)) { @@ -152,9 +156,12 @@ export class txs { t.From = address; t.To = this.Address; t.ID = txid; + if (change.length == 1) { + t.Change = change[0]; + } possibles.set(address, t); } else { - console.log(156, vin) + console.log(156, vin); } } } @@ -174,5 +181,6 @@ export class txo { To: string; Amount: number; Height: number; + Change: string; constructor() {} } diff --git a/src/routes/buymerits/+page.svelte b/src/routes/buymerits/+page.svelte index ea33790..582fad2 100644 --- a/src/routes/buymerits/+page.svelte +++ b/src/routes/buymerits/+page.svelte @@ -7,7 +7,7 @@ import { currentUser } from '@/stores/session'; import { NDKEvent } from '@nostr-dev-kit/ndk'; import { Avatar } from '@nostr-dev-kit/ndk-svelte-components'; - import { onDestroy } from 'svelte'; + import { onDestroy, onMount } from 'svelte'; import { derived } from 'svelte/store'; import AssociateBitcoinAddress from '../../components/AssociateBitcoinAddress.svelte'; import Heading from '../../components/Heading.svelte'; @@ -89,7 +89,7 @@ for (let [address, txo] of txs.From()) { for (let [_, ba] of r.BitcoinAssociations()) { if (ba.Address == txo.From) { - return new MeritPurchase(r, amrAuction, ba.Pubkey, txo.ID, txo.Amount); + return new MeritPurchase(r, amrAuction, ba.Pubkey, txo); } } } From 1cb0a9b4bd45f05542b0b135e26cbc0f373000c8 Mon Sep 17 00:00:00 2001 From: gsovereignty Date: Mon, 12 Aug 2024 13:50:27 +0800 Subject: [PATCH 6/7] problem: we are not publishing merit transfers --- src/components/AssociateBitcoinAddress.svelte | 4 +- src/components/BuyAMR.svelte | 71 +++++++++++++++++++ src/routes/buymerits/+page.svelte | 22 ++++-- 3 files changed, 88 insertions(+), 9 deletions(-) create mode 100644 src/components/BuyAMR.svelte diff --git a/src/components/AssociateBitcoinAddress.svelte b/src/components/AssociateBitcoinAddress.svelte index 5e48ce1..a71c424 100644 --- a/src/components/AssociateBitcoinAddress.svelte +++ b/src/components/AssociateBitcoinAddress.svelte @@ -65,8 +65,8 @@ their Merits.
{#if $associatedAddresses.size == 0}You do not have any registered addresses{:else} Your registered addresses: -
    - {#each $associatedAddresses as address}
  • {address}
  • {/each} +
      + {#each $associatedAddresses as address}
    • {address}
    • {/each}
    {/if} Add a new address now diff --git a/src/components/BuyAMR.svelte b/src/components/BuyAMR.svelte new file mode 100644 index 0000000..fbd9fcb --- /dev/null +++ b/src/components/BuyAMR.svelte @@ -0,0 +1,71 @@ + + + + Buy Now + + {#if !currentUser} + + + Heads up! + You need a nostr signing extension to use Nostrocket! + + {:else} + + Buy Merits from + +

    + To buy these merits you MUST send {auction.Merits / 100000000} BTC from one of your registered + addresses to {auction.RxAddress}. +

    +

    + Once the transaction has 2 confirmations the Merits will automatically be transferred to + your npub. +

    + + Todo: ask user to publish an event before making transaction so that multiple people don't + pay for the same Merits. + + + {/if} +
    +
    diff --git a/src/routes/buymerits/+page.svelte b/src/routes/buymerits/+page.svelte index 582fad2..b1b1c47 100644 --- a/src/routes/buymerits/+page.svelte +++ b/src/routes/buymerits/+page.svelte @@ -1,5 +1,4 @@