Skip to content

Commit

Permalink
cashu wip
Browse files Browse the repository at this point in the history
  • Loading branch information
riccardobl committed Nov 22, 2024
1 parent 1ef9b9f commit 78b1e64
Show file tree
Hide file tree
Showing 16 changed files with 822 additions and 12 deletions.
6 changes: 5 additions & 1 deletion .env.development
Original file line number Diff line number Diff line change
Expand Up @@ -168,4 +168,8 @@ CPU_SHARES_IMPORTANT=1024
CPU_SHARES_MODERATE=512
CPU_SHARES_LOW=256

NEXT_TELEMETRY_DISABLED=1
NEXT_TELEMETRY_DISABLED=1

CASHU_MINT_PORT=3338

NOSTR_PORT=7777
9 changes: 8 additions & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,12 @@
"port": 9229,
"outputCapture": "std"
}
]
],
"typescript.preferences.importModuleSpecifier": "relative",
"typescript.tsserver.experimental.enableProjectDiagnostics": true,
"typescript.tsserver.log": "verbose",
"typescript.tsserver.trace": "messages",
"typescript.tsserver.useSeparateSyntaxServer": true,
"typescript.tsserver.useSyntaxServer": "always",
"typescript.tsserver.experimental.enableProjectDiagnostics": true
}
25 changes: 25 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -653,6 +653,30 @@ services:
CONNECT: "localhost:${LNBITS_WEB_PORT}"
TORDIR: "/app/.tor"
cpu_shares: "${CPU_SHARES_LOW}"
cashu_mint:
image: cashubtc/nutshell:0.16.3
container_name: cashu_mint
restart: unless-stopped
depends_on:
lnd:
condition: service_healthy
restart: true
ports:
- "${CASHU_MINT_PORT}:3338"
environment:
- MINT_BACKEND_BOLT11_SAT=LndRPCWallet
- MINT_LISTEN_HOST=0.0.0.0
- MINT_LISTEN_PORT=3338
- MINT_PRIVATE_KEY=ca47300443b0102324fbee24cd1d04914ed11a901ca7061755283e57d9fceae3
- MINT_LND_RPC_ENDPOINT=lnd:10009
- MINT_LND_RPC_CERT=/lnd/tls.cert
- MINT_LND_RPC_MACAROON=/lnd/data/chain/bitcoin/regtest/admin.macaroon
- DEBUG=true
- TOR=false
- NOSTR_RELAYS=["wss://relay.primal.net"]
volumes:
- lnd:/lnd
command: ["poetry", "run", "mint"]
volumes:
db:
os:
Expand All @@ -664,3 +688,4 @@ volumes:
nwc_send:
nwc_recv:
tordata:
nostr:
144 changes: 144 additions & 0 deletions docker/nostr/strfry.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
##
## Default strfry config
##

# Directory that contains the strfry LMDB database (restart required)
db = "./strfry-db/"

dbParams {
# Maximum number of threads/processes that can simultaneously have LMDB transactions open (restart required)
maxreaders = 256

# Size of mmap() to use when loading LMDB (default is 10TB, does *not* correspond to disk-space used) (restart required)
mapsize = 10995116277760

# Disables read-ahead when accessing the LMDB mapping. Reduces IO activity when DB size is larger than RAM. (restart required)
noReadAhead = false
}

events {
# Maximum size of normalised JSON, in bytes
maxEventSize = 65536

# Events newer than this will be rejected
rejectEventsNewerThanSeconds = 900

# Events older than this will be rejected
rejectEventsOlderThanSeconds = 94608000

# Ephemeral events older than this will be rejected
rejectEphemeralEventsOlderThanSeconds = 60

# Ephemeral events will be deleted from the DB when older than this
ephemeralEventsLifetimeSeconds = 300

# Maximum number of tags allowed
maxNumTags = 2000

# Maximum size for tag values, in bytes
maxTagValSize = 1024
}

relay {
# Interface to listen on. Use 0.0.0.0 to listen on all interfaces (restart required)
bind = "0.0.0.0"

# Port to open for the nostr websocket protocol (restart required)
port = 7777

# Set OS-limit on maximum number of open files/sockets (if 0, don't attempt to set) (restart required)
nofiles = 1000000

# HTTP header that contains the client's real IP, before reverse proxying (ie x-real-ip) (MUST be all lower-case)
realIpHeader = ""

info {
# NIP-11: Name of this server. Short/descriptive (< 30 characters)
name = "strfry default"

# NIP-11: Detailed information about relay, free-form
description = "This is a strfry instance."

# NIP-11: Administrative nostr pubkey, for contact purposes
pubkey = ""

# NIP-11: Alternative administrative contact (email, website, etc)
contact = ""

# NIP-11: URL pointing to an image to be used as an icon for the relay
icon = ""

# List of supported lists as JSON array, or empty string to use default. Example: "[1,2]"
nips = ""
}

# Maximum accepted incoming websocket frame size (should be larger than max event) (restart required)
maxWebsocketPayloadSize = 131072

# Websocket-level PING message frequency (should be less than any reverse proxy idle timeouts) (restart required)
autoPingSeconds = 55

# If TCP keep-alive should be enabled (detect dropped connections to upstream reverse proxy)
enableTcpKeepalive = false

# How much uninterrupted CPU time a REQ query should get during its DB scan
queryTimesliceBudgetMicroseconds = 10000

# Maximum records that can be returned per filter
maxFilterLimit = 500

# Maximum number of subscriptions (concurrent REQs) a connection can have open at any time
maxSubsPerConnection = 20

writePolicy {
# If non-empty, path to an executable script that implements the writePolicy plugin logic
plugin = ""
}

compression {
# Use permessage-deflate compression if supported by client. Reduces bandwidth, but slight increase in CPU (restart required)
enabled = true

# Maintain a sliding window buffer for each connection. Improves compression, but uses more memory (restart required)
slidingWindow = true
}

logging {
# Dump all incoming messages
dumpInAll = false

# Dump all incoming EVENT messages
dumpInEvents = false

# Dump all incoming REQ/CLOSE messages
dumpInReqs = false

# Log performance metrics for initial REQ database scans
dbScanPerf = false

# Log reason for invalid event rejection? Can be disabled to silence excessive logging
invalidEvents = true
}

numThreads {
# Ingester threads: route incoming requests, validate events/sigs (restart required)
ingester = 3

# reqWorker threads: Handle initial DB scan for events (restart required)
reqWorker = 3

# reqMonitor threads: Handle filtering of new events (restart required)
reqMonitor = 3

# negentropy threads: Handle negentropy protocol messages (restart required)
negentropy = 2
}

negentropy {
# Support negentropy protocol messages
enabled = true

# Maximum records that sync will process before returning an error
maxSyncEvents = 1000000
}
}
1 change: 1 addition & 0 deletions jsconfig.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"compilerOptions": {
"sourceMap": true,
"baseUrl": ".",
"paths": {
"@/api/*": [
Expand Down
127 changes: 118 additions & 9 deletions lib/nostr.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ export const DEFAULT_CROSSPOSTING_RELAYS = [
'wss://nostr.mutinywallet.com/',
'wss://relay.mutinywallet.com/'
]

export const RELAYS_BLACKLIST = []

/**
Expand All @@ -31,27 +32,135 @@ export class Nostr {
/**
* @type {NDK}
*/
ndk = null
defaultSigner = null

_ndk = null
_privKey = null
_relays = []
constructor ({ privKey, defaultSigner, relays, supportNip07 = true, ...ndkOptions } = {}) {
this.ndk = new NDK({
if (typeof window !== 'undefined' && process.env.NODE_ENV === 'development') {
window.localStorage.debug = 'ndk:*,ndk-wallet:*'
}
this._privKey = privKey
this._relays = relays?.sort()
this._ndk = new NDK({
explicitRelayUrls: relays,
blacklistRelayUrls: RELAYS_BLACKLIST,
autoConnectUserRelays: false,
autoFetchUserMutelist: false,
clientName: 'stacker.news',
signer: defaultSigner ?? this.selectSigner({ privKey, supportNip07 }),
...ndkOptions
})
this.defaultSigner = defaultSigner ?? (privKey ? new NDKPrivateKeySigner(privKey) : null) ?? (supportNip07 && typeof window !== 'undefined' && window?.nostr ? new NDKNip07Signer() : null)
}

close () {
// TODO: how?
}

checkConfig ({ privKey, relays }) {
relays = relays?.sort()
if (this._privKey !== privKey) return false
if (this._relays?.length !== relays?.length) return false
if (this._relays?.some((r, i) => r !== relays[i])) return false
return true
}

selectSigner ({ privKey, supportNip07 = true } = {}) {
return (privKey ? new NDKPrivateKeySigner(privKey) : null) ?? (supportNip07 && typeof window !== 'undefined' && window?.nostr ? new NDKNip07Signer() : null)
}

/**
* @returns {Promise<string>}
*/
get pubKey () {
return this._ndk.signer.user().then(u => u.pubkey)
}

/**
* @returns {Promise<Array<string>>}
*/
get relays () {
return this._relays
}

/**
* @returns {string|undefined}
*/
get privKey () {
return this._privKey
}

/**
* @returns {NDKSigner}
*/
get signer () {
return this._ndk.signer
}

/**
* Get a nwc wallet
* @param {string} nwcUrl
* @returns {Promise<NDKNwc>}
*/
async nwc (nwcUrl) {
return await this.ndk.nwc(nwcUrl)
return await this._ndk.nwc(nwcUrl)
}

get ndk () {
return this._ndk
}

/**
* Subscribe to events
* @import {NDKFilter} from '@nostr-dev-kit/ndk'
* @import {NDKEvent} from '@nostr-dev-kit/ndk'
* @param {NDKFilter[]|NDKFilter} filters
* @param {NDKEvent): void} onEvent
* @param {Object} options
* @param {Array<string>} options.relays
* @param {boolean} [options.closeOnEose]
*/
subscribe (filters, { onEvent, onEose, relays, closeOnEose = false, waitClose = false } = {}) {
const ndk = this._ndk
const relaySet = NDKRelaySet.fromRelayUrls(relays, ndk, true)

const sub = ndk.subscribe(filters, {
skipOptimisticPublishEvent: true
}, relaySet)

if (onEvent) {
sub.on('event', (event) => {
const r = onEvent(sub, event)
if (r instanceof Promise) r.catch(console.error)
})
}

const closingPromise = new Promise((resolve, reject) => {
sub.on('close', () => {
resolve()
})
})
sub.wait = async () => closingPromise

// code is a bit fonky, but it is to make sure
// sub is closed only after onEose is called
// and that if onEose is a promise, the exception is caught
if (onEose || closeOnEose) {
sub.on('eose', () => {
let r
if (onEose) {
r = onEose(sub)
}
if (r instanceof Promise) {
r.catch(console.error).finally(() => {
if (closeOnEose) sub.stop()
})
} else {
if (closeOnEose) sub.stop()
}
})
}

return sub
}

/**
Expand All @@ -67,13 +176,13 @@ export class Nostr {
*/
/* eslint-disable camelcase */
async sign ({ kind, created_at, content, tags }, { privKey, signer } = {}) {
const event = new NDKEvent(this.ndk)
const event = new NDKEvent(this._ndk)
event.kind = kind
event.created_at = created_at
event.content = content
event.tags = tags

signer = signer ?? (privKey ? new NDKPrivateKeySigner(privKey) : this.defaultSigner)
signer = signer ?? (privKey ? new NDKPrivateKeySigner(privKey) : this._ndk.signer)
if (!signer) throw new Error('no way to sign this event, please provide a signer or private key')
await event.sign(signer)
return event
Expand All @@ -99,7 +208,7 @@ export class Nostr {
const successfulRelays = []
const failedRelays = []

const relaySet = NDKRelaySet.fromRelayUrls(relays, this.ndk, true)
const relaySet = NDKRelaySet.fromRelayUrls(relays, this._ndk, true)

event.on('relay:publish:failed', (relay, error) => {
failedRelays.push({ relay: relay.url, error })
Expand Down
Loading

0 comments on commit 78b1e64

Please sign in to comment.