diff --git a/src/controllers/MetadataController.ts b/src/controllers/MetadataController.ts index b9557bb..bf1b3d8 100644 --- a/src/controllers/MetadataController.ts +++ b/src/controllers/MetadataController.ts @@ -1,11 +1,8 @@ -import NDK, { NDKEvent, NDKSubscription } from '@nostr-dev-kit/ndk' import { Event, - EventTemplate, Filter, VerifiedEvent, kinds, - nip19, validateEvent, verifyEvent } from 'nostr-tools' @@ -13,7 +10,7 @@ import { toast } from 'react-toastify' import { EventEmitter } from 'tseep' import { NostrController, relayController } from '.' import { localCache } from '../services' -import { NostrJoiningBlock, ProfileMetadata, RelaySet } from '../types' +import { ProfileMetadata, RelaySet } from '../types' import { findRelayListAndUpdateCache, findRelayListInCache, @@ -21,7 +18,6 @@ import { getMostPopularRelays, getUserRelaySet, isOlderThanOneWeek, - queryNip05, unixNow } from '../utils' @@ -218,128 +214,6 @@ export class MetadataController extends EventEmitter { }) } - public getNostrJoiningBlockNumber = async ( - hexKey: string - ): Promise => { - const relaySet = await this.findRelayListMetadata(hexKey) - - const userRelays: string[] = [] - - // find user's relays - if (relaySet.write.length > 0) { - userRelays.push(...relaySet.write) - } else { - const metadata = await this.findMetadata(hexKey) - if (!metadata) return null - - const metadataContent = this.extractProfileMetadataContent(metadata) - - if (metadataContent?.nip05) { - const nip05Profile = await queryNip05(metadataContent.nip05) - - if (nip05Profile && nip05Profile.pubkey === hexKey) { - userRelays.push(...nip05Profile.relays) - } - } - } - - if (userRelays.length === 0) return null - - // filter for finding user's first kind 0 event - const eventFilter: Filter = { - kinds: [kinds.Metadata], - authors: [hexKey] - } - - // find user's kind 0 event published on user's relays - const event = await relayController.fetchEvent(eventFilter, userRelays) - - if (event) { - const { created_at } = event - - // initialize job request - const jobEventTemplate: EventTemplate = { - content: '', - created_at: unixNow(), - kind: 68001, - tags: [ - ['i', `${created_at * 1000}`], - ['j', 'blockChain-block-number'] - ] - } - - // sign job request event - const jobSignedEvent = - await this.nostrController.signEvent(jobEventTemplate) - - const relays = [ - 'wss://relay.damus.io', - 'wss://relay.primal.net', - 'wss://relayable.org' - ] - - await relayController.publish(jobSignedEvent, relays).catch((err) => { - console.error( - 'Error occurred in publish blockChain-block-number DVM job', - err - ) - }) - - const subscribeWithTimeout = ( - subscription: NDKSubscription, - timeoutMs: number - ): Promise => { - return new Promise((resolve, reject) => { - const eventHandler = (event: NDKEvent) => { - subscription.stop() - resolve(event.content) - } - - subscription.on('event', eventHandler) - - // Set up a timeout to stop the subscription after a specified time - const timeout = setTimeout(() => { - subscription.stop() // Stop the subscription - reject(new Error('Subscription timed out')) // Reject the promise with a timeout error - }, timeoutMs) - - // Handle subscription close event - subscription.on('close', () => clearTimeout(timeout)) - }) - } - - const dvmNDK = new NDK({ - explicitRelayUrls: relays - }) - - await dvmNDK.connect(2000) - - // filter for getting DVM job's result - const sub = dvmNDK.subscribe({ - kinds: [68002 as number], - '#e': [jobSignedEvent.id], - '#p': [jobSignedEvent.pubkey] - }) - - // asynchronously get block number from dvm job with 20 seconds timeout - const dvmJobResult = await subscribeWithTimeout(sub, 20000) - - const encodedEventPointer = nip19.neventEncode({ - id: event.id, - relays: userRelays, - author: event.pubkey, - kind: event.kind - }) - - return { - block: parseInt(dvmJobResult), - encodedEventPointer - } - } - - return null - } - public validate = (event: Event) => validateEvent(event) && verifyEvent(event) public getEmptyMetadataEvent = (): Event => { diff --git a/src/pages/profile/index.tsx b/src/pages/profile/index.tsx index c1822b9..a7b205b 100644 --- a/src/pages/profile/index.tsx +++ b/src/pages/profile/index.tsx @@ -12,7 +12,12 @@ import { MetadataController } from '../../controllers' import { getProfileSettingsRoute } from '../../routes' import { State } from '../../store/rootReducer' import { NostrJoiningBlock, ProfileMetadata } from '../../types' -import { getRoboHashPicture, hexToNpub, shorten } from '../../utils' +import { + getNostrJoiningBlockNumber, + getRoboHashPicture, + hexToNpub, + shorten +} from '../../utils' import styles from './style.module.scss' import { Container } from '../../components/Container' @@ -51,8 +56,7 @@ export const ProfilePage = () => { useEffect(() => { if (pubkey) { - metadataController - .getNostrJoiningBlockNumber(pubkey) + getNostrJoiningBlockNumber(pubkey) .then((res) => { setNostrJoiningBlock(res) }) diff --git a/src/pages/settings/profile/index.tsx b/src/pages/settings/profile/index.tsx index 9b2fc2d..8723c2e 100644 --- a/src/pages/settings/profile/index.tsx +++ b/src/pages/settings/profile/index.tsx @@ -26,7 +26,11 @@ import { setMetadataEvent } from '../../../store/actions' import { LoadingSpinner } from '../../../components/LoadingSpinner' import { LoginMethods } from '../../../store/auth/types' import { SmartToy } from '@mui/icons-material' -import { getRoboHashPicture, unixNow } from '../../../utils' +import { + getNostrJoiningBlockNumber, + getRoboHashPicture, + unixNow +} from '../../../utils' import { Container } from '../../../components/Container' export const ProfileSettingsPage = () => { @@ -71,8 +75,7 @@ export const ProfileSettingsPage = () => { useEffect(() => { if (pubkey) { - metadataController - .getNostrJoiningBlockNumber(pubkey) + getNostrJoiningBlockNumber(pubkey) .then((res) => { setNostrJoiningBlock(res) }) diff --git a/src/utils/dvm.ts b/src/utils/dvm.ts new file mode 100644 index 0000000..ba985ed --- /dev/null +++ b/src/utils/dvm.ts @@ -0,0 +1,135 @@ +import { EventTemplate, Filter, kinds, nip19 } from 'nostr-tools' +import { queryNip05, unixNow } from '.' +import { + MetadataController, + NostrController, + relayController +} from '../controllers' +import { NostrJoiningBlock } from '../types' +import NDK, { NDKEvent, NDKSubscription } from '@nostr-dev-kit/ndk' + +export const getNostrJoiningBlockNumber = async ( + hexKey: string +): Promise => { + const metadataController = new MetadataController() + + const relaySet = await metadataController.findRelayListMetadata(hexKey) + + const userRelays: string[] = [] + + // find user's relays + if (relaySet.write.length > 0) { + userRelays.push(...relaySet.write) + } else { + const metadata = await metadataController.findMetadata(hexKey) + if (!metadata) return null + + const metadataContent = + metadataController.extractProfileMetadataContent(metadata) + + if (metadataContent?.nip05) { + const nip05Profile = await queryNip05(metadataContent.nip05) + + if (nip05Profile && nip05Profile.pubkey === hexKey) { + userRelays.push(...nip05Profile.relays) + } + } + } + + if (userRelays.length === 0) return null + + // filter for finding user's first kind 0 event + const eventFilter: Filter = { + kinds: [kinds.Metadata], + authors: [hexKey] + } + + // find user's kind 0 event published on user's relays + const event = await relayController.fetchEvent(eventFilter, userRelays) + + if (event) { + const { created_at } = event + + // initialize job request + const jobEventTemplate: EventTemplate = { + content: '', + created_at: unixNow(), + kind: 68001, + tags: [ + ['i', `${created_at * 1000}`], + ['j', 'blockChain-block-number'] + ] + } + + const nostrController = NostrController.getInstance() + + // sign job request event + const jobSignedEvent = await nostrController.signEvent(jobEventTemplate) + + const relays = [ + 'wss://relay.damus.io', + 'wss://relay.primal.net', + 'wss://relayable.org' + ] + + await relayController.publish(jobSignedEvent, relays).catch((err) => { + console.error( + 'Error occurred in publish blockChain-block-number DVM job', + err + ) + }) + + const subscribeWithTimeout = ( + subscription: NDKSubscription, + timeoutMs: number + ): Promise => { + return new Promise((resolve, reject) => { + const eventHandler = (event: NDKEvent) => { + subscription.stop() + resolve(event.content) + } + + subscription.on('event', eventHandler) + + // Set up a timeout to stop the subscription after a specified time + const timeout = setTimeout(() => { + subscription.stop() // Stop the subscription + reject(new Error('Subscription timed out')) // Reject the promise with a timeout error + }, timeoutMs) + + // Handle subscription close event + subscription.on('close', () => clearTimeout(timeout)) + }) + } + + const dvmNDK = new NDK({ + explicitRelayUrls: relays + }) + + await dvmNDK.connect(2000) + + // filter for getting DVM job's result + const sub = dvmNDK.subscribe({ + kinds: [68002 as number], + '#e': [jobSignedEvent.id], + '#p': [jobSignedEvent.pubkey] + }) + + // asynchronously get block number from dvm job with 20 seconds timeout + const dvmJobResult = await subscribeWithTimeout(sub, 20000) + + const encodedEventPointer = nip19.neventEncode({ + id: event.id, + relays: userRelays, + author: event.pubkey, + kind: event.kind + }) + + return { + block: parseInt(dvmJobResult), + encodedEventPointer + } + } + + return null +} diff --git a/src/utils/index.ts b/src/utils/index.ts index 16b36f5..accc008 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,4 +1,5 @@ export * from './crypto' +export * from './dvm' export * from './hash' export * from './localStorage' export * from './mark'