ReadonlyoptsReadonlyoptionsStaticdefaultStaticlistenerReadonlycxnReadonlycxnStaticlistenerStaticlistenerStaticlistenerStaticdefaultStaticephemeralStaticfromOptionalabort: AbortControllerStaticgetStaticlistenerStaticdefaultStaticlistenerOptionalpubkeyStaticfromStaticfromCreates an instance of Encryptable.
-An EventTemplate with optional tags and content.
-Plaintext updates to be applied to the event content.
-Here's an example which enables updating a private mute list:
-const event = {kind: 10000, content: "", tags: []} // An event, only kind is required
const encryptable = new Encryptable(event, {content: JSON.stringify([["e", "bad word"]])})
const eventTemplate = await encryptable.reconcile(myEncryptFunction)
-
-
-StaticdefaultReadonlyxsReadonlyxsStaticcreateStaticfromStaticfromStaticfromStaticfromIMetaStaticwrapOptionalmappings: TagFeedMapping[]Optionalmappings: TagFeedMapping[]Creates object from array of key-value pairs
-Normalize a URL.
-URLs with custom protocols are not normalized and just passed through by default. Supported protocols are: https, http, file, and data.
Optionalopts: OptionsCreates new object excluding entries with specified values
-Creates throttled version of function
-Safely executes function and handles errors
-Optionalprofile: RelayProfileEvents are parameterized replaceable, which means that, for each combination of pubkey, kind and the d tag, only the latest event is expected to be stored by relays, older versions are expected to be discarded.
A nostr toolkit focused on creating highly a configurable client system, extracted from the Coracle nostr client.
-This is a monorepo which is split into several different packages:
-If you're developing an application which requires changes to welshman, you'll need to use npm link to link the two. This is an annoying process, and is only supported if using npm.
package directory in welshman, run npm linkrm -rf node_modules; npm i; cat package.json|js '.depedencies|keys[]'|grep welshman|xargs npm link.If you run npm install in your application directory, you'll need to repeat the final step above.
Utilities for dealing with svelte stores when using welshman.
-import {ctx, setContext} from '@welshman/lib'
import {getNip07} from '@welshman/signer'
import {throttled} from '@welshman/store'
import {createEvent, NOTE} from '@welshman/util'
import {
getDefaultNetContext,
getDefaultAppContext,
signer,
pubkey,
publishThunk,
load,
initStorage,
storageAdapters,
freshness,
plaintext,
repository,
tracker,
} from '@welshman/app'
// Set up app config
setContext({
net: getDefaultNetContext(),
app: getDefaultAppContext(),
})
// Log in via NIP 07
addSession({method: 'nip07', pubkey: await getNip07().getPubkey()})
// Signer is ready to go
const event = signer.get().encrypt(/* ... */)
// This will fetch the user's profile automatically, and return an observable that updates
// automatically. Several different stores exist that are ready to go, including handles,
// zappers, relaySelections, relays, follows, mutes.
const profile = deriveProfile(pubkey.get())
// A global router helps make intelligent relay selections
const router = ctx.app.router
// Publish is done using thunks, which optimistically publish to the local database, deferring
// signing and publishing for instant user feedback. Progress is reported as relays accept/reject the event
const thunk = publishThunk({
relays: router.FromUser().getUrls(),
event: createEvent(NOTE, {content: "hi"}),
delay: 3000,
})
// Thunks can be aborted until after `delay`, allowing for soft-undo
thunk.controller.abort()
// Subscriptions automatically infer relays using `router` if not provided. If the request can be cached,
// results from the local repository are returned immediately. `subscribe` and `load` are both available
const events = await load({filters: [{kinds: [NOTE]}])
// Some commands are included
const thunk = follow('97c70a44366a6535c145b333f973ea86dfdc2d7a99da618c40c64705ad98e322')
// Stores can be easily synchronized with indexeddb. Freshness keeps track of how stale the caches are,
// plaintext maps encrypted events to their decrypted content, repository and tracker hold events and
// event/relay mappings, respectively.
const ready = initStorage("my-db", 1, {
relays: {keyPath: "url", store: throttled(3000, relays)},
handles: {keyPath: "nip05", store: throttled(3000, handles)},
freshness: storageAdapters.fromObjectStore(freshness, {throttle: 3000}),
plaintext: storageAdapters.fromObjectStore(plaintext, {throttle: 3000}),
events: storageAdapters.fromRepositoryAndTracker(repository, tracker, {throttle: 3000}),
})
-
-
-Utilities for parsing and rendering note content. Customizable via RenderOptions.
-import {parse, render} from '@welshman/content'
const content = "Hello<br>from https://coracle.tools! <script>alert('evil')</script>"
const parsed = parse({content, tags: []})
// [
// { type: 'text', value: 'Hello<br>from ', raw: 'Hello<br>from ' },
// {
// type: 'link',
// value: { url: URL, isMedia: false },
// raw: 'https://coracle.tools'
// },
// {
// type: 'text',
// value: "! <script>alert('evil')</script>",
// raw: "! <script>alert('evil')</script>"
// }
// ]
const result = renderAsText(parsed)
// => Hello<br>from https://coracle.tools/! <script>alert('evil')</script>
const result = renderAsHtml(parsed)
// => Hello<br>from <a href="https://coracle.tools/" target="_blank">coracle.tools/</a>! <script>alert('evil')</script>
-
-
-Utilities for building nostr DVMs.
-import type {Publish, Subscription} from '@welshman/net'
import {makeDvmRequest, DVMEvent} from '@welshman/dvm'
const req = makeDvmRequest({
// Create and sign a dvm request event, including any desired tags
event: createAndSign({kind: 5300}),
// Publish and subscribe to these relays
relays: ['wss://relay.damus.io', 'wss://dvms.f7z.io'],
// Timeout defaults to 30 seconds
timeout: 30_000,
// Auto close on first result (defaults to true)
autoClose: true,
// Listen for and emit `progress` events
reportProgress: true,
})
// Listen for progress, result, etc
req.emitter.on(DVMEvent.Progress, (url, event) => console.log(event))
req.emitter.on(DVMEvent.Result, (url, event) => console.log(event))
-
-
-const {bytesToHex} = require('@noble/hashes/utils')
const {generateSecretKey} = require('nostr-tools')
const {createEvent} = require('@welshman/util')
const {subscribe} = require('@welshman/net')
const {DVM} = require('@welshman/dvm')
// Your DVM's private key. Store this somewhere safe
// const hexPrivateKey = bytesToHex(generateSecretKey())
const hexPrivateKey = '9cd387a3aa0c1abc2ef517c8402f29c069b4174e02a426491aec7566501bee67'
// Tags that we'll return as content discovery suggestions
const tags = []
// Populate the tags with music by Ainsley Costello
const sub = subscribe({
timeout: 30_000,
relays: ["wss://relay.wavlake.com"],
filters: [{
kinds: [31337],
'#p': ['8806372af51515bf4aef807291b96487ea1826c966a5596bca86697b5d8b23bc'],
}],
})
// Push event ids to our suggestions
sub.emitter.on('event', (url, e) => tags.push(["e", e.id, url]))
const dvm = new DVM({
// The private key used to sign events
sk: hexPrivateKey,
// Relays that the DVM will listen on
relays: ['wss://relay.damus.io', 'wss://dvms.f7z.io'],
// Only listen to requests tagging our dvm
requireMention: true,
// Expire results after 1 hour (the default)
expireAfter: 60 * 60,
// Handlers for various kinds
handlers: {
5300: dvm => ({
handleEvent: async function* (event) {
// DVM responses are stringified into the content
const content = JSON.stringify(tags)
// Yield our response. Kind 7000 can be used for partial results too
yield createEvent(event.kind + 1000, {content})
},
}),
}
})
// Enable logging
dvm.logEvents = true
// When you're ready
dvm.start()
// When you're done
dvm.stop()
-
-
-A custom feed compiler and loader for nostr. Read the spec on wikifreedia.
-// Define a feed using set operations
const feed = intersectionFeed(
unionFeed(
dvmFeed({
kind: 5300,
pubkey: '19b78ccfa7c5e31e6bacbb3f2a1703f64b62017702e584440bf29a7e16263e8c',
}),
listFeed("10003:19ba654f26afd4930fd3d51baf4e26f1413b7aeec7190cd6c0cdf4d2f14cec6b:"),
)
wotFeed({min: 0.1}),
scopeFeed("global"),
)
// Create a controller, providing required context via FeedOptions
const controller = new FeedController({
feed,
request,
requestDVM,
getPubkeysForScope,
getPubkeysForWOTRange,
onEvent: event => console.log("Event", event),
onExhausted: () => console.log("Exhausted"),
})
// Load notes using the feed
const events = await controller.load(10)
-
-
-Some general-purpose utilities for use in @welshman apps.
-Includes:
-ctx variable which can be used for global configurationdefer utilityUtilities having to do with connection management and nostr messages.
-import {ctx, setContext} from '@welshman/lib'
import {type TrustedEvent, createEvent, NOTE} from '@welshman/util'
import {subscribe, publish, getDefaultNetContext} from '@welshman/net'
// Sets up customizable event valdation, handlers, etc
setContext(getDefaultNetContext())
// Send a subscription
const sub = subscribe({
relays: ['wss://relay.example.com/'],
filters: [{kinds: [1], limit: 1}],
closeOnEose: true,
timeout: 10000,
})
sub.emitter.on(SubscriptionEvent.Event, (url: string, event: TrustedEvent) => {
console.log(url, event)
sub.close()
})
// Publish an event
const pub = publish({
relays: ['wss://relay.example.com/'],
event: createEvent(NOTE, {content: 'hi'}),
})
pub.emitter.on('*', (status: PublishStatus, url: string) => {
console.log(status, url)
})
// The Tracker class can tell you which relays an event was read from or published to
console.log(ctx.net.tracker.getRelays(event.id))
-
-
-The main reason this module exists is to support different backends via Executor and different target classes. For example, to add a local relay that automatically gets used:
import {setContext} from '@welshman/lib'
import {LOCAL_RELAY_URL, Relay, Repository} from '@welshman/util'
import {getDefaultNetContext, Multi, Local, Relays, Executor} from '@welshman/net'
const repository = new Repository()
const relay = new Relay(repository)
setContext(getDefaultNetContext({
getExecutor: (relays: string[]) => {
return new Executor(
new Multi([
new Local(relay),
new Relays(remoteUrls.map(url => ctx.net.pool.get(url))),
])
)
},
}))
-
-
-Implementations of signer utilities and classes.
-import {makeSecret, Nip01Signer} from '@welshman/signer'
const signer = Nip01Signer.fromSecret(makeSecret())
-
-
-import {getNip07, Nip07Signer} from '@welshman/signer'
if (getNip07()) {
const signer = new Nip07Signer()
}
-
-
-import {getNip07, Nip07Signer} from '@welshman/signer'
if (getNip07()) {
const signer = new Nip07Signer()
}
-
-
-import {createEvent, NOTE} from '@welshman/util'
import {makeSecret, Nip46Broker, Nip46Signer} from '@welshman/signer'
const clientSecret = makeSecret()
const relays = ['wss://relay.signer.example/']
const broker = Nip46Broker.get({relays, clientSecret})
const signer = new Nip46Signer(broker)
const ncUrl = broker.makeNostrconnectUrl({name: "My app"})
const abortController = new AbortController()
let response
try {
response = await broker.waitForNostrconnect(url, abortController)
} catch (e: any) {
if (e?.error) {
showWarning(`Received error from signer: ${e.error}`)
} else if (e) {
console.error(e)
}
}
if (response) {
// Now we know the bunker's pubkey and can do stuff with the signer
const signerPubkey = response.event.pubkey
// Next time we want to use our signer, we can instantiate it like so:
const newBroker = Nip46Broker.get({relays, clientSecret, signerPubkey})
const newSigner = new Nip46Signer(newBroker)
}
-
-
-import {createEvent, NOTE, DIRECT_MESSAGE} from '@welshman/util'
const signer = // Create your signer...
const nip59 = Nip59.fromSigner(signer)
// Sign an event
const event = await signer.sign(createEvent(NOTE, {content: "hi"}))
// Wrap a NIP 17 DM
const rumor = await nip59.wrap(recipientPubkey, createEvent(DIRECT_MESSAGE, {content: "hi"}))
// Note that it returns a rumor; be sure to publish the `wrap`
const wrap = rumor.wrap
-
-
-Utilities for dealing with svelte stores when using welshman.
-import {Repository, NAMED_PEOPLE, NAMED_TOPICS, type TrustedEvent, readUserList, List} from '@welshman/util'
import {deriveEventsMapped} from '@welshman/store'
const repository = new Repository()
// Create a svelte store that performantly maps matching events in the repository to List objects
const lists = deriveEventsMapped<PublishedUserList>(repository, {
filters: [{kinds: [NAMED_PEOPLE, NAMED_TOPICS]}],
eventToItem: (event: TrustedEvent) => (event.tags.length > 1 ? readUserList(event) : null),
itemToEvent: (list: List) => list.event,
})
-
-
-Some nostr-specific utilities. For the most part, these will not have side effects or manage state. Includes:
-Encryptable for ensuring payloads get encryptedRetrieves fallback relays, for use when no other relays can be selected.
-OptionalgetIndexerRelays?: () => string[]Retrieves relays that index profiles and relay selections.
-OptionalgetLimit?: () => numberRetrieves the limit setting, which is the maximum number of relays that should be -returned from getUrls and getSelections.
-OptionalgetPubkeyRelays?: (pubkey: string, mode?: RelayMode) => string[]Retrieves relays for the specified public key and mode.
-OptionalgetRelayQuality?: (url: string) => numberRetrieves the quality of the specified relay.
-OptionalgetSearchRelays?: () => string[]Retrieves relays likely to support NIP-50 search.
-OptionalgetUserPubkey?: () => string | nullRetrieves the user's public key.
-Type representing null or undefined
-ConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstOne day seconds
-ConstOne hour in seconds
-ConstOne minute in seconds
-ConstOne month in seconds (approximate)
-ConstOne quarter in seconds (approximate)
-ConstOne week in seconds
-ConstOne year in seconds (approximate)
-ConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConstConst
Represents an encryptable event with optional updates.
-