Clean up wot

This commit is contained in:
Jon Staab
2026-06-17 14:56:39 -07:00
parent 28219eb64f
commit f5124a6c4e
18 changed files with 178 additions and 136 deletions
+2 -2
View File
@@ -9,7 +9,7 @@ import {
removeFromList,
} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import {RepositoryCollection} from "./repositoryCollection.js"
import {Collection} from "./collection.js"
import {Network} from "./network.js"
import {Router} from "./router.js"
import {User} from "./user.js"
@@ -21,7 +21,7 @@ import type {IClient} from "./client.js"
* so it depends on the relay-list collection. Feeds `RelayStats.getQuality` so
* blocked relays are never selected.
*/
export class BlockedRelayLists extends RepositoryCollection<ReturnType<typeof readList>> {
export class BlockedRelayLists extends Collection<ReturnType<typeof readList>> {
constructor(ctx: IClient) {
super(ctx, {
filters: [{kinds: [BLOCKED_RELAYS]}],
+2 -2
View File
@@ -1,6 +1,6 @@
import {BLOSSOM_SERVERS, asDecryptedEvent, readList} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import {RepositoryCollection} from "./repositoryCollection.js"
import {Collection} from "./collection.js"
import {Network} from "./network.js"
import type {IClient} from "./client.js"
@@ -8,7 +8,7 @@ import type {IClient} from "./client.js"
* Blossom server lists (kind 10063), keyed by pubkey. Loaded via the outbox
* model (the author's write relays), so it depends on the relay-list collection.
*/
export class BlossomServerLists extends RepositoryCollection<ReturnType<typeof readList>> {
export class BlossomServerLists extends Collection<ReturnType<typeof readList>> {
constructor(ctx: IClient) {
super(ctx, {
filters: [{kinds: [BLOSSOM_SERVERS]}],
+4 -4
View File
@@ -18,10 +18,10 @@ export class ClientData<T> {
protected index = writable(new Map<string, T>())
protected getIndex = getter(this.index)
protected itemSubscribers: ((key: string, value: Maybe<T>) => void)[] = []
public derive: (key?: string, ...args: any[]) => Readable<Maybe<T>>
public derived: (key?: string, ...args: any[]) => Readable<Maybe<T>>
constructor(protected readonly ctx: IClient) {
this.derive = makeDeriveItem(this.index)
this.derived = makeDeriveItem(this.index)
}
subscribe = this.index.subscribe
@@ -83,7 +83,7 @@ export class ClientData<T> {
/**
* A `ClientData` collection that knows how to lazily load items by key from the
* network. Subclasses implement `fetch`; `load`/`forceLoad`/`derive` are derived
* network. Subclasses implement `fetch`; `load`/`forceLoad`/`derived` are derived
* from it (with per-key caching and backoff via `makeLoadItem`).
*/
export abstract class LoadableData<T> extends ClientData<T> {
@@ -102,6 +102,6 @@ export abstract class LoadableData<T> extends ClientData<T> {
this.load = makeLoadItem(fetch, this.get, options)
this.forceLoad = makeForceLoadItem(fetch, this.get)
this.derive = makeDeriveItem(this.index, this.load)
this.derived = makeDeriveItem(this.index, this.load)
}
}
@@ -6,7 +6,7 @@ import type {EventToItem, ItemsByKey, MakeLoadItemOptions} from "@welshman/store
import type {IClient} from "./client.js"
import {Stores} from "./stores.js"
export type RepositoryCollectionOptions<T> = {
export type CollectionOptions<T> = {
filters: Filter[]
eventToItem: EventToItem<T>
getKey: (item: T) => string
@@ -22,7 +22,7 @@ export type RepositoryCollectionOptions<T> = {
*
* Like `ClientData`, subclasses depend only on the `IClient` seam.
*/
export abstract class RepositoryCollection<T> {
export abstract class Collection<T> {
byKey: Readable<ItemsByKey<T>>
all: Readable<T[]>
subscribe: Readable<ItemsByKey<T>>["subscribe"]
@@ -32,9 +32,6 @@ export abstract class RepositoryCollection<T> {
values: () => IterableIterator<T>
load: (key: string, ...args: any[]) => Promise<Maybe<T>>
forceLoad: (key: string, ...args: any[]) => Promise<Maybe<T>>
// Reactive view of a single key that also triggers a load
derive: (key?: string, ...args: any[]) => Readable<Maybe<T>>
// Reactive view of a single key that does not trigger a load
derived: (key?: string, ...args: any[]) => Readable<Maybe<T>>
private getByKey: () => ItemsByKey<T>
@@ -42,7 +39,7 @@ export abstract class RepositoryCollection<T> {
constructor(
protected readonly ctx: IClient,
options: RepositoryCollectionOptions<T>,
options: CollectionOptions<T>,
) {
const fetch = (key: string, ...args: any[]) => this.fetch(key, ...args)
@@ -60,8 +57,7 @@ export abstract class RepositoryCollection<T> {
this.values = () => this.getByKey().values()
this.load = makeLoadItem(fetch, this.get, options.loadOptions)
this.forceLoad = makeForceLoadItem(fetch, this.get)
this.derive = makeDeriveItem(this.byKey, this.load)
this.derived = makeDeriveItem(this.byKey)
this.derived = makeDeriveItem(this.byKey, this.load)
}
// Convenience views of the current user's own item (replaces the old
@@ -73,7 +69,7 @@ export abstract class RepositoryCollection<T> {
return pubkey ? this.get(pubkey) : undefined
}
deriveForUser = (...args: any[]) => this.derive(this.ctx.user?.pubkey, ...args)
deriveForUser = (...args: any[]) => this.derived(this.ctx.user?.pubkey, ...args)
loadForUser = (...args: any[]) => {
const pubkey = this.ctx.user?.pubkey
+5 -5
View File
@@ -26,11 +26,11 @@ export class Feeds {
case Scope.Self:
return [$pubkey]
case Scope.Follows:
return this.ctx.use(Wot).getFollows($pubkey)
return this.ctx.use(Wot).deriveFollows($pubkey).get()
case Scope.Network:
return this.ctx.use(Wot).getNetwork($pubkey)
return this.ctx.use(Wot).deriveNetwork($pubkey).get()
case Scope.Followers:
return this.ctx.use(Wot).getFollowers($pubkey)
return this.ctx.use(Wot).deriveFollowers($pubkey).get()
default:
return []
}
@@ -38,11 +38,11 @@ export class Feeds {
getPubkeysForWOTRange = (min: number, max: number): string[] => {
const pubkeys = []
const $maxWot = this.ctx.use(Wot).getMaxWot() ?? 0
const $maxWot = this.ctx.use(Wot).max.get() ?? 0
const thresholdMin = $maxWot * min
const thresholdMax = $maxWot * max
for (const [tpk, score] of this.ctx.use(Wot).getWotGraph().entries()) {
for (const [tpk, score] of this.ctx.use(Wot).graph.get().entries()) {
if (score >= thresholdMin && score <= thresholdMax) {
pubkeys.push(tpk)
}
+2 -2
View File
@@ -7,7 +7,7 @@ import {
removeFromList,
} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import {RepositoryCollection} from "./repositoryCollection.js"
import {Collection} from "./collection.js"
import {Network} from "./network.js"
import {Thunks} from "./thunk.js"
import {User} from "./user.js"
@@ -17,7 +17,7 @@ import type {IClient} from "./client.js"
* Kind-3 follow lists, keyed by pubkey. Loaded via the outbox model (the
* author's write relays), so it depends on the relay-list collection.
*/
export class FollowLists extends RepositoryCollection<ReturnType<typeof readList>> {
export class FollowLists extends Collection<ReturnType<typeof readList>> {
constructor(ctx: IClient) {
super(ctx, {
filters: [{kinds: [FOLLOWS]}],
+1 -1
View File
@@ -65,7 +65,7 @@ export class Handles extends LoadableData<Handle> {
this.loadForPubkey(pubkey, relays)
return deriveDeduplicated(
[this.index, this.ctx.use(Profiles).derive(pubkey, relays)],
[this.index, this.ctx.use(Profiles).derived(pubkey, relays)],
([$handlesByNip05, $profile]) => {
if (!$profile?.nip05) return undefined
+1 -1
View File
@@ -3,7 +3,7 @@ export * from "./policies.js"
export * from "./network.js"
export * from "./stores.js"
export * from "./clientData.js"
export * from "./repositoryCollection.js"
export * from "./collection.js"
export * from "./user.js"
export * from "./router.js"
export * from "./relays.js"
+2 -2
View File
@@ -8,7 +8,7 @@ import {
removeFromList,
} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import {RepositoryCollection} from "./repositoryCollection.js"
import {Collection} from "./collection.js"
import {Network} from "./network.js"
import {Router} from "./router.js"
import {User} from "./user.js"
@@ -20,7 +20,7 @@ import type {IClient} from "./client.js"
* outbox model (the author's write relays), so it depends on the relay-list
* collection.
*/
export class MessagingRelayLists extends RepositoryCollection<ReturnType<typeof readList>> {
export class MessagingRelayLists extends Collection<ReturnType<typeof readList>> {
constructor(ctx: IClient) {
super(ctx, {
filters: [{kinds: [MESSAGING_RELAYS]}],
+2 -2
View File
@@ -9,7 +9,7 @@ import {
updateList,
} from "@welshman/util"
import type {TrustedEvent, PublishedList} from "@welshman/util"
import {RepositoryCollection} from "./repositoryCollection.js"
import {Collection} from "./collection.js"
import type {IClient} from "./client.js"
import {Network} from "./network.js"
import {Thunks} from "./thunk.js"
@@ -20,7 +20,7 @@ import {User} from "./user.js"
* Kind-10000 mute lists, keyed by pubkey. Mute lists carry private entries in
* encrypted content, so decoding goes through the plaintext cache.
*/
export class MuteLists extends RepositoryCollection<PublishedList> {
export class MuteLists extends Collection<PublishedList> {
constructor(ctx: IClient) {
super(ctx, {
filters: [{kinds: [MUTES]}],
+2 -2
View File
@@ -7,7 +7,7 @@ import {
removeFromList,
} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import {RepositoryCollection} from "./repositoryCollection.js"
import {Collection} from "./collection.js"
import {Network} from "./network.js"
import {Thunks} from "./thunk.js"
import {User} from "./user.js"
@@ -17,7 +17,7 @@ import type {IClient} from "./client.js"
* NIP-51 pin lists (kind 10001), keyed by pubkey. Loaded via the outbox model
* (the author's write relays), so it depends on the relay-list collection.
*/
export class PinLists extends RepositoryCollection<ReturnType<typeof readList>> {
export class PinLists extends Collection<ReturnType<typeof readList>> {
constructor(ctx: IClient) {
super(ctx, {
filters: [{kinds: [PINS]}],
+3 -3
View File
@@ -9,7 +9,7 @@ import {
PROFILE,
} from "@welshman/util"
import type {Profile} from "@welshman/util"
import {RepositoryCollection} from "./repositoryCollection.js"
import {Collection} from "./collection.js"
import {Network} from "./network.js"
import {Router} from "./router.js"
import {Thunks} from "./thunk.js"
@@ -19,7 +19,7 @@ import type {IClient} from "./client.js"
* Kind-0 profiles, keyed by pubkey. Loaded via the outbox model (the author's
* write relays), resolved through the relay-list collection at fetch time.
*/
export class Profiles extends RepositoryCollection<ReturnType<typeof readProfile>> {
export class Profiles extends Collection<ReturnType<typeof readProfile>> {
constructor(ctx: IClient) {
super(ctx, {
filters: [{kinds: [PROFILE]}],
@@ -45,7 +45,7 @@ export class Profiles extends RepositoryCollection<ReturnType<typeof readProfile
deriveDisplay = (pubkey: string | undefined, ...args: any[]) =>
pubkey
? derived(this.derive(pubkey, ...args), $profile =>
? derived(this.derived(pubkey, ...args), $profile =>
displayProfile($profile, displayPubkey(pubkey)),
)
: readable("")
+2 -2
View File
@@ -12,7 +12,7 @@ import {
makeEvent,
} from "@welshman/util"
import type {TrustedEvent, PublishedList} from "@welshman/util"
import {RepositoryCollection} from "./repositoryCollection.js"
import {Collection} from "./collection.js"
import {Router, addMinimalFallbacks} from "./router.js"
import {Network} from "./network.js"
import {User} from "./user.js"
@@ -23,7 +23,7 @@ import type {IClient} from "./client.js"
* NIP-65 relay lists, keyed by pubkey. This is the routing substrate every other
* outbox-model load depends on (see `Network.loadUsingOutbox`).
*/
export class RelayLists extends RepositoryCollection<PublishedList> {
export class RelayLists extends Collection<PublishedList> {
constructor(ctx: IClient) {
super(ctx, {
filters: [{kinds: [RELAYS]}],
+2 -2
View File
@@ -82,9 +82,9 @@ export class Searches {
onSearch: this.searchProfiles,
getValue: (profile: PublishedProfile) => profile.event.pubkey,
sortFn: ({score = 1, item}) => {
const wotScore = this.ctx.use(Wot).getWotGraph().get(item.event.pubkey) || 0
const wotScore = this.ctx.use(Wot).graph.get().get(item.event.pubkey) || 0
return dec(score) * inc(wotScore / (this.ctx.use(Wot).getMaxWot() || 1))
return dec(score) * inc(wotScore / (this.ctx.use(Wot).max.get() || 1))
},
fuseOptions: {
keys: [
+2 -2
View File
@@ -8,7 +8,7 @@ import {
removeFromList,
} from "@welshman/util"
import type {TrustedEvent} from "@welshman/util"
import {RepositoryCollection} from "./repositoryCollection.js"
import {Collection} from "./collection.js"
import {Network} from "./network.js"
import {Router} from "./router.js"
import {User} from "./user.js"
@@ -20,7 +20,7 @@ import type {IClient} from "./client.js"
* outbox model (the author's write relays), so it depends on the relay-list
* collection.
*/
export class SearchRelayLists extends RepositoryCollection<ReturnType<typeof readList>> {
export class SearchRelayLists extends Collection<ReturnType<typeof readList>> {
constructor(ctx: IClient) {
super(ctx, {
filters: [{kinds: [SEARCH_RELAYS]}],
+2 -1
View File
@@ -2,6 +2,7 @@ import {readable} from "svelte/store"
import type {Readable} from "svelte/store"
import {on} from "@welshman/lib"
import {getTopicTagValues} from "@welshman/util"
import type {RepositoryUpdate} from "@welshman/net"
import {deriveItems} from "@welshman/store"
import type {IClient} from "./client.js"
@@ -38,7 +39,7 @@ export class Topics {
}
this.byName = readable(topicsByName, set =>
on(ctx.repository, "update", ({added}: {added: {tags: string[][]}[]}) => {
on(ctx.repository, "update", ({added}: RepositoryUpdate) => {
let dirty = false
for (const event of added) {
+137 -92
View File
@@ -1,132 +1,177 @@
import {derived, writable} from "svelte/store"
import type {Readable, Writable} from "svelte/store"
import {readable, derived} from "svelte/store"
import {max, throttle, addToMapKey, inc, dec} from "@welshman/lib"
import {getListTags, getPubkeyTagValues} from "@welshman/util"
import {throttled, getter} from "@welshman/store"
import type {List} from "@welshman/util"
import {withGetter} from "@welshman/store"
import type {ReadableWithGetter} from "@welshman/store"
import type {IClient} from "./client.js"
import {FollowLists} from "./follows.js"
import {MuteLists} from "./mutes.js"
const listPubkeys = (list: List | undefined) => getPubkeyTagValues(getListTags(list))
/**
* Web-of-trust scoring derived from follow and mute lists. The trust graph is
* built from the perspective of the client's user (or, with no user, the union
* of every known follow list) and updated reactively as lists change.
*
* Every query is exposed as a reactive store — the aggregate `*ByPubkey`/`graph`/
* `max` fields and the parameterized `derive*` methods. All of them are wrapped
* in `withGetter`, so a synchronous snapshot is just `<store>.get()` /
* `derive*(...).get()`.
*/
export class Wot {
followersByPubkey: Readable<Map<string, Set<string>>>
mutersByPubkey: Readable<Map<string, Set<string>>>
wotGraph: Writable<Map<string, number>>
maxWot: Readable<number | undefined>
private getFollowersByPubkeyStore: () => Map<string, Set<string>>
private getMutersByPubkeyStore: () => Map<string, Set<string>>
private getWotGraphStore: () => Map<string, number>
private getMaxWotStore: () => number | undefined
followersByPubkey: ReadableWithGetter<Map<string, Set<string>>>
mutersByPubkey: ReadableWithGetter<Map<string, Set<string>>>
graph: ReadableWithGetter<Map<string, number>>
max: ReadableWithGetter<number | undefined>
constructor(readonly ctx: IClient) {
const followLists = this.ctx.use(FollowLists)
const muteLists = this.ctx.use(MuteLists)
this.followersByPubkey = withGetter(
readable(new Map<string, Set<string>>(), set =>
this.ctx.use(FollowLists).subscribe(
throttle(1000, lists => {
const $followersByPubkey = new Map<string, Set<string>>()
this.followersByPubkey = derived(throttled(1000, followLists.all), lists => {
const $followersByPubkey = new Map<string, Set<string>>()
for (const list of lists.values()) {
for (const pubkey of getPubkeyTagValues(getListTags(list))) {
addToMapKey($followersByPubkey, pubkey, list.event.pubkey)
}
}
for (const list of lists) {
for (const pubkey of getPubkeyTagValues(getListTags(list))) {
addToMapKey($followersByPubkey, pubkey, list.event.pubkey)
}
}
set($followersByPubkey)
}),
),
),
)
return $followersByPubkey
})
this.mutersByPubkey = withGetter(
readable(new Map<string, Set<string>>(), set =>
this.ctx.use(MuteLists).subscribe(
throttle(1000, lists => {
const $mutersByPubkey = new Map<string, Set<string>>()
this.mutersByPubkey = derived(throttled(1000, muteLists.all), lists => {
const $mutersByPubkey = new Map<string, Set<string>>()
for (const list of lists.values()) {
for (const pubkey of getPubkeyTagValues(getListTags(list))) {
addToMapKey($mutersByPubkey, pubkey, list.event.pubkey)
}
}
for (const list of lists) {
for (const pubkey of getPubkeyTagValues(getListTags(list))) {
addToMapKey($mutersByPubkey, pubkey, list.event.pubkey)
}
}
set($mutersByPubkey)
}),
),
),
)
return $mutersByPubkey
})
this.graph = withGetter(
readable(new Map<string, number>(), set => {
const rebuild = throttle(1000, () => {
const followLists = this.ctx.use(FollowLists)
const muteLists = this.ctx.use(MuteLists)
const $pubkey = this.ctx.user?.pubkey
const $graph = new Map<string, number>()
const $follows = $pubkey
? listPubkeys(followLists.get($pubkey))
: Array.from(followLists.keys())
this.wotGraph = writable(new Map<string, number>())
for (const follow of $follows) {
for (const pubkey of listPubkeys(followLists.get(follow))) {
$graph.set(pubkey, inc($graph.get(pubkey)))
}
this.maxWot = derived(this.wotGraph, $g => max(Array.from($g.values())))
for (const pubkey of listPubkeys(muteLists.get(follow))) {
$graph.set(pubkey, dec($graph.get(pubkey)))
}
}
this.getFollowersByPubkeyStore = getter(this.followersByPubkey)
this.getMutersByPubkeyStore = getter(this.mutersByPubkey)
this.getWotGraphStore = getter(this.wotGraph)
this.getMaxWotStore = getter(this.maxWot)
set($graph)
})
followLists.subscribe(this.buildGraph)
muteLists.subscribe(this.buildGraph)
const unsubscribers = [
this.ctx.use(FollowLists).subscribe(rebuild),
this.ctx.use(MuteLists).subscribe(rebuild),
]
return () => unsubscribers.forEach(unsubscribe => unsubscribe())
}),
)
this.max = withGetter(derived(this.graph, $g => max(Array.from($g.values()))))
}
getFollows = (pubkey: string) =>
getPubkeyTagValues(getListTags(this.ctx.use(FollowLists).get(pubkey)))
deriveFollows = (pubkey: string) =>
withGetter(derived(this.ctx.use(FollowLists), $lists => listPubkeys($lists.get(pubkey))))
getMutes = (pubkey: string) =>
getPubkeyTagValues(getListTags(this.ctx.use(MuteLists).get(pubkey)))
deriveMutes = (pubkey: string) =>
withGetter(derived(this.ctx.use(MuteLists), $lists => listPubkeys($lists.get(pubkey))))
getNetwork = (pubkey: string) => {
const pubkeys = new Set(this.getFollows(pubkey))
const network = new Set<string>()
deriveNetwork = (pubkey: string) =>
withGetter(
derived(this.ctx.use(FollowLists), $lists => {
const pubkeys = new Set(listPubkeys($lists.get(pubkey)))
const network = new Set<string>()
for (const follow of pubkeys) {
for (const tpk of this.getFollows(follow)) {
if (!pubkeys.has(tpk)) {
network.add(tpk)
for (const follow of pubkeys) {
for (const tpk of listPubkeys($lists.get(follow))) {
if (!pubkeys.has(tpk)) {
network.add(tpk)
}
}
}
}
}
return Array.from(network)
}
return Array.from(network)
}),
)
getFollowersByPubkey = () => this.getFollowersByPubkeyStore()
deriveFollowers = (pubkey: string) =>
withGetter(derived(this.followersByPubkey, $followers => Array.from($followers.get(pubkey) || [])))
getMutersByPubkey = () => this.getMutersByPubkeyStore()
deriveMuters = (pubkey: string) =>
withGetter(derived(this.mutersByPubkey, $muters => Array.from($muters.get(pubkey) || [])))
getFollowers = (pubkey: string) => Array.from(this.getFollowersByPubkey().get(pubkey) || [])
deriveFollowsWhoFollow = (pubkey: string, target: string) =>
withGetter(
derived(this.ctx.use(FollowLists), $lists =>
listPubkeys($lists.get(pubkey)).filter(other =>
listPubkeys($lists.get(other)).includes(target),
),
),
)
getMuters = (pubkey: string) => Array.from(this.getMutersByPubkey().get(pubkey) || [])
deriveFollowsWhoMute = (pubkey: string, target: string) =>
withGetter(
derived([this.ctx.use(FollowLists), this.ctx.use(MuteLists)], ([$follows, $mutes]) =>
listPubkeys($follows.get(pubkey)).filter(other =>
listPubkeys($mutes.get(other)).includes(target),
),
),
)
getFollowsWhoFollow = (pubkey: string, target: string) =>
this.getFollows(pubkey).filter(other => this.getFollows(other).includes(target))
deriveWotScore = (pubkey: string, target: string) =>
withGetter(
derived(
[
this.ctx.use(FollowLists),
this.ctx.use(MuteLists),
this.followersByPubkey,
this.mutersByPubkey,
],
([$follows, $mutes, $followers, $muters]) => {
let follows: string[]
let mutes: string[]
getFollowsWhoMute = (pubkey: string, target: string) =>
this.getFollows(pubkey).filter(other => this.getMutes(other).includes(target))
if (pubkey) {
const theirFollows = listPubkeys($follows.get(pubkey))
getWotGraph = () => this.getWotGraphStore()
follows = theirFollows.filter(other => listPubkeys($follows.get(other)).includes(target))
mutes = theirFollows.filter(other => listPubkeys($mutes.get(other)).includes(target))
} else {
follows = Array.from($followers.get(target) || [])
mutes = Array.from($muters.get(target) || [])
}
getMaxWot = () => this.getMaxWotStore()
buildGraph = throttle(1000, () => {
const $pubkey = this.ctx.user?.pubkey
const $graph = new Map<string, number>()
const $follows = $pubkey
? this.getFollows($pubkey)
: Array.from(this.ctx.use(FollowLists).keys())
for (const follow of $follows) {
for (const pubkey of this.getFollows(follow)) {
$graph.set(pubkey, inc($graph.get(pubkey)))
}
for (const pubkey of this.getMutes(follow)) {
$graph.set(pubkey, dec($graph.get(pubkey)))
}
}
this.wotGraph.set($graph)
})
getWotScore = (pubkey: string, target: string) => {
const follows = pubkey ? this.getFollowsWhoFollow(pubkey, target) : this.getFollowers(target)
const mutes = pubkey ? this.getFollowsWhoMute(pubkey, target) : this.getMuters(target)
return follows.length - mutes.length
}
return follows.length - mutes.length
},
),
)
}
+2 -2
View File
@@ -71,7 +71,7 @@ export class Zappers extends LoadableData<Zapper> {
this.loadForPubkey(pubkey, relays)
return deriveDeduplicated(
[this.index, this.ctx.use(Profiles).derive(pubkey, relays)],
[this.index, this.ctx.use(Profiles).derived(pubkey, relays)],
([$zappersByLnurl, $profile]) =>
$profile?.lnurl ? $zappersByLnurl.get($profile.lnurl) : undefined,
)
@@ -116,7 +116,7 @@ export class Zappers extends LoadableData<Zapper> {
const stores: Readable<any>[] = [
this.index,
...splits.map(split => profiles.derive(split.pubkey)),
...splits.map(split => profiles.derived(split.pubkey)),
]
return deriveDeduplicatedByValue(stores, (values: any[]) => {