mirror of
https://github.com/Permissionless-Software-Foundation/bch-dex-taker-v2.git
synced 2026-09-21 16:52:01 -07:00
feat(nostr): Created nostr views
This commit is contained in:
@@ -3,7 +3,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// Global npm libraries
|
// Global npm libraries
|
||||||
import React, { useCallback, useEffect, useState } from 'react'
|
import React, { useState } from 'react'
|
||||||
import { Container, Row, Col, Card } from 'react-bootstrap'
|
import { Container, Row, Col, Card } from 'react-bootstrap'
|
||||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
import { faWallet, faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons'
|
import { faWallet, faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons'
|
||||||
@@ -12,9 +12,6 @@ import { faWallet, faEye, faEyeSlash } from '@fortawesome/free-solid-svg-icons'
|
|||||||
// Local libraries
|
// Local libraries
|
||||||
import './wallet-summary.css'
|
import './wallet-summary.css'
|
||||||
import CopyOnClick from './copy-on-click'
|
import CopyOnClick from './copy-on-click'
|
||||||
import { getPublicKey } from 'nostr-tools/pure'
|
|
||||||
import { base58_to_binary as base58ToBinary } from 'base58-js'
|
|
||||||
import { bytesToHex } from '@noble/hashes/utils' // already an installed dependency
|
|
||||||
|
|
||||||
function WalletSummary (props) {
|
function WalletSummary (props) {
|
||||||
// Props
|
// Props
|
||||||
@@ -28,10 +25,7 @@ function WalletSummary (props) {
|
|||||||
const [blurredPrivateKey, setBlurredPrivateKey] = useState(true)
|
const [blurredPrivateKey, setBlurredPrivateKey] = useState(true)
|
||||||
const [blurredNostrPrivKey, setBlurredNostrPrivKey] = useState(true)
|
const [blurredNostrPrivKey, setBlurredNostrPrivKey] = useState(true)
|
||||||
|
|
||||||
const [nostrKeyPair, setNostrKeyPair] = useState({
|
const [nostrKeyPair] = useState(bchWalletState.nostrKeyPair)
|
||||||
privHex: '',
|
|
||||||
pubHex: ''
|
|
||||||
})
|
|
||||||
|
|
||||||
// Encapsulate component state into an object that can be passed to child functions
|
// Encapsulate component state into an object that can be passed to child functions
|
||||||
const walletSummaryData = {
|
const walletSummaryData = {
|
||||||
@@ -82,30 +76,6 @@ function WalletSummary (props) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const nostrKeyPairFromWIF = useCallback((WIF) => {
|
|
||||||
if (!WIF) return
|
|
||||||
|
|
||||||
// Extract the privaty key from the WIF, using this guide:
|
|
||||||
// https://learnmeabitcoin.com/technical/keys/private-key/wif/
|
|
||||||
const wifBuf = base58ToBinary(WIF)
|
|
||||||
const privBuf = wifBuf.slice(1, 33)
|
|
||||||
// console.log('privBuf: ', privBuf)
|
|
||||||
|
|
||||||
const privHex = bytesToHex(privBuf)
|
|
||||||
console.log('BCH & Nostr private key (HEX format): ', privHex)
|
|
||||||
|
|
||||||
const pubHex = getPublicKey(privBuf)
|
|
||||||
console.log('nostrPubKey: ', pubHex)
|
|
||||||
|
|
||||||
return {
|
|
||||||
privHex,
|
|
||||||
pubHex
|
|
||||||
}
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setNostrKeyPair(nostrKeyPairFromWIF(bchWalletState.privateKey))
|
|
||||||
}, [nostrKeyPairFromWIF, bchWalletState])
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Container>
|
<Container>
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ import SlpTokens from './slp-tokens'
|
|||||||
import SweepWif from './sweep/index.js'
|
import SweepWif from './sweep/index.js'
|
||||||
import SignMessage from './sign/index.js'
|
import SignMessage from './sign/index.js'
|
||||||
import ServerSelectView from './configuration/select-server-view'
|
import ServerSelectView from './configuration/select-server-view'
|
||||||
|
import NostrPost from './nostr/nostr-post.js'
|
||||||
|
import NostrRead from './nostr/nostr-read.js'
|
||||||
|
|
||||||
function AppBody (props) {
|
function AppBody (props) {
|
||||||
// Dependency injection through props
|
// Dependency injection through props
|
||||||
@@ -43,6 +45,9 @@ function AppBody (props) {
|
|||||||
<Route path='/sweep' element={<SweepWif appData={appData} />} />
|
<Route path='/sweep' element={<SweepWif appData={appData} />} />
|
||||||
<Route path='/sign' element={<SignMessage appData={appData} />} />
|
<Route path='/sign' element={<SignMessage appData={appData} />} />
|
||||||
<Route path='/configuration' element={<ServerSelectView appData={appData} />} />
|
<Route path='/configuration' element={<ServerSelectView appData={appData} />} />
|
||||||
|
<Route path='/nostr-post' element={<NostrPost appData={appData} />} />
|
||||||
|
<Route path='/nostr-read' element={<NostrRead appData={appData} />} />
|
||||||
|
|
||||||
</Routes>
|
</Routes>
|
||||||
{/** Show in all paths except the servers view */}
|
{/** Show in all paths except the servers view */}
|
||||||
{/* {appData.currentPath !== '/servers' && <SelectServerButton linkTo='/servers' appData={appData} />} */}
|
{/* {appData.currentPath !== '/servers' && <SelectServerButton linkTo='/servers' appData={appData} />} */}
|
||||||
|
|||||||
@@ -0,0 +1,158 @@
|
|||||||
|
/*
|
||||||
|
Component for posting nostr information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Global npm libraries
|
||||||
|
import React, { useState } from 'react'
|
||||||
|
import { Container, Form, Button, Spinner } from 'react-bootstrap'
|
||||||
|
import Accordion from 'react-bootstrap/Accordion'
|
||||||
|
import { finalizeEvent } from 'nostr-tools/pure'
|
||||||
|
import { Relay } from 'nostr-tools/relay'
|
||||||
|
import { hexToBytes } from '@noble/hashes/utils' // already an installed dependency
|
||||||
|
|
||||||
|
function NostrPost (props) {
|
||||||
|
const [accordionKey, setAccordionKey] = useState('1')
|
||||||
|
const [onFetch, setOnFetch] = useState(false)
|
||||||
|
const { bchWalletState } = props.appData
|
||||||
|
const [formData, setFormData] = useState({
|
||||||
|
name: '',
|
||||||
|
about: ''
|
||||||
|
})
|
||||||
|
|
||||||
|
const [errorMsg, setErrorMsg] = useState('')
|
||||||
|
const [successMsg, setSuccessMsg] = useState('')
|
||||||
|
|
||||||
|
const handleInputChange = (e) => {
|
||||||
|
const { name, value } = e.target
|
||||||
|
setFormData({
|
||||||
|
...formData,
|
||||||
|
[name]: value
|
||||||
|
})
|
||||||
|
|
||||||
|
setSuccessMsg('')
|
||||||
|
setErrorMsg('')
|
||||||
|
}
|
||||||
|
// Post on nostr network
|
||||||
|
const handleSubmit = async (e) => {
|
||||||
|
e.preventDefault()
|
||||||
|
try {
|
||||||
|
setErrorMsg('')
|
||||||
|
setSuccessMsg('')
|
||||||
|
setOnFetch(true)
|
||||||
|
|
||||||
|
const { nostrKeyPair } = bchWalletState
|
||||||
|
|
||||||
|
// Convert private key to binary
|
||||||
|
const privateKeyBin = hexToBytes(nostrKeyPair.privHex)
|
||||||
|
|
||||||
|
// Relay list
|
||||||
|
const psf = 'wss://nostr-relay.psfoundation.info'
|
||||||
|
|
||||||
|
const formDataString = JSON.stringify(formData)
|
||||||
|
|
||||||
|
// Generate a post.
|
||||||
|
const eventTemplate = {
|
||||||
|
kind: 0,
|
||||||
|
created_at: Math.floor(Date.now() / 1000),
|
||||||
|
tags: [],
|
||||||
|
content: formDataString
|
||||||
|
}
|
||||||
|
console.log(`eventTemplate: ${JSON.stringify(eventTemplate, null, 2)}`)
|
||||||
|
|
||||||
|
// Sign the post
|
||||||
|
const signedEvent = finalizeEvent(eventTemplate, privateKeyBin)
|
||||||
|
console.log('signedEvent: ', signedEvent)
|
||||||
|
|
||||||
|
// Connect to a relay.
|
||||||
|
const relay = await Relay.connect(psf)
|
||||||
|
console.log(`connected to ${relay.url}`)
|
||||||
|
|
||||||
|
// Publish the message to the relay.
|
||||||
|
const result = await relay.publish(signedEvent)
|
||||||
|
console.log('result: ', result)
|
||||||
|
|
||||||
|
// Close the connection to the relay.
|
||||||
|
relay.close()
|
||||||
|
resetForm()
|
||||||
|
setSuccessMsg('Post successfully published!')
|
||||||
|
setOnFetch(false)
|
||||||
|
} catch (error) {
|
||||||
|
console.warn(error)
|
||||||
|
setErrorMsg(error.message || 'An error occurred while posting')
|
||||||
|
setOnFetch(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleAccordionChange = (key) => {
|
||||||
|
setAccordionKey(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
const resetForm = () => {
|
||||||
|
setFormData({
|
||||||
|
name: '',
|
||||||
|
about: ''
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Container>
|
||||||
|
<Accordion activeKey={accordionKey} onSelect={handleAccordionChange}>
|
||||||
|
<Accordion.Item eventKey='1'>
|
||||||
|
<Accordion.Header>Post</Accordion.Header>
|
||||||
|
<Accordion.Body>
|
||||||
|
{errorMsg && (
|
||||||
|
<div className='alert alert-danger' role='alert'>
|
||||||
|
{errorMsg}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{successMsg && (
|
||||||
|
<div className='alert alert-success' role='alert'>
|
||||||
|
{successMsg}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<Form onSubmit={handleSubmit}>
|
||||||
|
<Form.Group className='mb-3'>
|
||||||
|
<Form.Label>Name</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
type='text'
|
||||||
|
placeholder='Enter your name'
|
||||||
|
name='name'
|
||||||
|
value={formData.name}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
|
||||||
|
<Form.Group className='mb-3'>
|
||||||
|
<Form.Label>About</Form.Label>
|
||||||
|
<Form.Control
|
||||||
|
as='textarea'
|
||||||
|
rows={3}
|
||||||
|
placeholder='Tell us about yourself'
|
||||||
|
name='about'
|
||||||
|
value={formData.about}
|
||||||
|
onChange={handleInputChange}
|
||||||
|
required
|
||||||
|
/>
|
||||||
|
</Form.Group>
|
||||||
|
|
||||||
|
<div className='d-flex justify-content-center'>
|
||||||
|
{!onFetch && (
|
||||||
|
<Button variant='primary' type='submit' disabled={onFetch}>
|
||||||
|
Post
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{onFetch && <Spinner animation='border' variant='primary' />}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</Form>
|
||||||
|
</Accordion.Body>
|
||||||
|
</Accordion.Item>
|
||||||
|
</Accordion>
|
||||||
|
</Container>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NostrPost
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
// Global npm libraries
|
||||||
|
import React, { useEffect, useState } from 'react'
|
||||||
|
import { Container } from 'react-bootstrap'
|
||||||
|
|
||||||
|
import Accordion from 'react-bootstrap/Accordion'
|
||||||
|
|
||||||
|
import { RelayPool } from 'nostr'
|
||||||
|
|
||||||
|
function NostrRead (props) {
|
||||||
|
const { bchWalletState } = props.appData
|
||||||
|
const [posts, setPosts] = useState([])
|
||||||
|
const [accordionKey, setAccordionKey] = useState('0')
|
||||||
|
const [loaded, setLoaded] = useState(false)
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Get Last post from a author
|
||||||
|
const start = () => {
|
||||||
|
const { nostrKeyPair } = bchWalletState
|
||||||
|
|
||||||
|
const psf = 'wss://nostr-relay.psfoundation.info'
|
||||||
|
|
||||||
|
const pool = RelayPool([psf])
|
||||||
|
pool.on('open', relay => {
|
||||||
|
relay.subscribe('subid', { limit: 10, kinds: [0], authors: [nostrKeyPair.pubHex] })
|
||||||
|
})
|
||||||
|
|
||||||
|
pool.on('eose', relay => {
|
||||||
|
console.log('Closing Relay')
|
||||||
|
relay.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
pool.on('event', (relay, subId, ev) => {
|
||||||
|
console.log('Received event:', ev)
|
||||||
|
setPosts(currentPosts => [...currentPosts, ev])
|
||||||
|
})
|
||||||
|
|
||||||
|
setLoaded(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!loaded) {
|
||||||
|
start()
|
||||||
|
}
|
||||||
|
}, [bchWalletState, loaded])
|
||||||
|
|
||||||
|
const handleAccordionChange = (key) => {
|
||||||
|
setAccordionKey(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Container>
|
||||||
|
<Accordion activeKey={accordionKey} onSelect={handleAccordionChange}>
|
||||||
|
<Accordion.Item eventKey='0'>
|
||||||
|
<Accordion.Header>Read ( {posts.length} post found )</Accordion.Header>
|
||||||
|
<Accordion.Body>
|
||||||
|
{posts.map((post, index) => (
|
||||||
|
<div key={index}>
|
||||||
|
<span>{post.content}</span>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</Accordion.Body>
|
||||||
|
</Accordion.Item>
|
||||||
|
</Accordion>
|
||||||
|
</Container>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default NostrRead
|
||||||
@@ -100,6 +100,20 @@ function NavMenu (props) {
|
|||||||
>
|
>
|
||||||
Configuration
|
Configuration
|
||||||
</NavLink>
|
</NavLink>
|
||||||
|
<NavLink
|
||||||
|
className={currentPath === '/nostr-post' ? 'nav-link-active' : 'nav-link-inactive'}
|
||||||
|
to='/nostr-post'
|
||||||
|
onClick={handleClickEvent}
|
||||||
|
>
|
||||||
|
Nostr Post
|
||||||
|
</NavLink>
|
||||||
|
<NavLink
|
||||||
|
className={currentPath === '/nostr-read' ? 'nav-link-active' : 'nav-link-inactive'}
|
||||||
|
to='/nostr-read'
|
||||||
|
onClick={handleClickEvent}
|
||||||
|
>
|
||||||
|
Nostr Profile
|
||||||
|
</NavLink>
|
||||||
</Nav>
|
</Nav>
|
||||||
</Navbar.Collapse>
|
</Navbar.Collapse>
|
||||||
</Navbar>
|
</Navbar>
|
||||||
|
|||||||
@@ -10,6 +10,9 @@ import BchDexLib from 'bch-dex-lib'
|
|||||||
import GistServers from './gist-servers'
|
import GistServers from './gist-servers'
|
||||||
import Nostr from './nostr'
|
import Nostr from './nostr'
|
||||||
import P2WDB from 'p2wdb'
|
import P2WDB from 'p2wdb'
|
||||||
|
import { base58_to_binary as base58ToBinary } from 'base58-js'
|
||||||
|
import { bytesToHex } from '@noble/hashes/utils' // already an installed dependency
|
||||||
|
import { getPublicKey } from 'nostr-tools/pure'
|
||||||
|
|
||||||
class AsyncLoad {
|
class AsyncLoad {
|
||||||
constructor () {
|
constructor () {
|
||||||
@@ -59,8 +62,14 @@ class AsyncLoad {
|
|||||||
await wallet.walletInfoPromise
|
await wallet.walletInfoPromise
|
||||||
await wallet.initialize()
|
await wallet.initialize()
|
||||||
console.log('starting to update wallet state.')
|
console.log('starting to update wallet state.')
|
||||||
|
|
||||||
|
// Get Nostr key pair from WIF
|
||||||
|
const nostrKeyPair = this.nostrKeyPairFromWIF(wallet.walletInfo.privateKey)
|
||||||
|
|
||||||
|
const walletInfo = wallet.walletInfo
|
||||||
|
walletInfo.nostrKeyPair = nostrKeyPair
|
||||||
// Update the state of the wallet.
|
// Update the state of the wallet.
|
||||||
appData.updateBchWalletState({ walletObj: wallet.walletInfo, appData })
|
appData.updateBchWalletState({ walletObj: walletInfo, appData })
|
||||||
console.log('finished updating wallet state.')
|
console.log('finished updating wallet state.')
|
||||||
// Save the mnemonic to local storage.
|
// Save the mnemonic to local storage.
|
||||||
if (!mnemonic) {
|
if (!mnemonic) {
|
||||||
@@ -246,6 +255,27 @@ class AsyncLoad {
|
|||||||
throw error
|
throw error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get Nostr key pair from WIF
|
||||||
|
nostrKeyPairFromWIF (WIF) {
|
||||||
|
if (!WIF) return
|
||||||
|
|
||||||
|
// Extract the privaty key from the WIF, using this guide:
|
||||||
|
// https://learnmeabitcoin.com/technical/keys/private-key/wif/
|
||||||
|
const wifBuf = base58ToBinary(WIF)
|
||||||
|
const privBuf = wifBuf.slice(1, 33)
|
||||||
|
// console.log('privBuf: ', privBuf)
|
||||||
|
|
||||||
|
const privHex = bytesToHex(privBuf)
|
||||||
|
console.log('BCH & Nostr private key (HEX format): ', privHex)
|
||||||
|
|
||||||
|
const pubHex = getPublicKey(privBuf)
|
||||||
|
|
||||||
|
return {
|
||||||
|
privHex,
|
||||||
|
pubHex
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function sleep (ms) {
|
function sleep (ms) {
|
||||||
|
|||||||
Reference in New Issue
Block a user