From 77d83349c1dcfd9fc8ee09d8290865f4e3178097 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Wed, 5 Mar 2025 19:03:14 -0800 Subject: [PATCH] Added bch-dex-lib and Nostr libraries --- package-lock.json | 8 ++ package.json | 2 + src/App.js | 10 ++ .../app-body/nfts-for-sale/buy-button.js | 79 +++++++++++++ .../app-body/nfts-for-sale/index.js | 1 + .../app-body/nfts-for-sale/info-button.js | 2 +- .../app-body/nfts-for-sale/token-card.js | 4 +- src/hooks/state.js | 9 +- src/services/async-load.js | 31 +++++ src/services/nostr.js | 110 ++++++++++++++++++ 10 files changed, 252 insertions(+), 4 deletions(-) create mode 100644 src/components/app-body/nfts-for-sale/buy-button.js create mode 100644 src/services/nostr.js diff --git a/package-lock.json b/package-lock.json index a0818bd..e049230 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "bch-dex-taker-v2", "version": "1.0.0", "dependencies": { + "@chris.troutner/nostr-tools": "2.10.4", "@chris.troutner/react-jdenticon": "1.0.1", "@fortawesome/fontawesome-svg-core": "6.7.2", "@fortawesome/free-regular-svg-icons": "6.7.2", @@ -16,6 +17,7 @@ "axios": "0.27.2", "bch-dex-lib": "2.0.2", "bch-message-lib": "2.2.1", + "bch-nostr": "1.3.4", "bootstrap": "5.2.0", "crypto-browserify": "3.12.1", "process": "0.11.10", @@ -1884,6 +1886,8 @@ }, "node_modules/@chris.troutner/nostr-tools": { "version": "2.10.4", + "resolved": "http://94.130.170.209:4873/@chris.troutner%2fnostr-tools/-/nostr-tools-2.10.4.tgz", + "integrity": "sha512-e8AYJ0yQ0O3bvnuXQUIdB+P3W7f06st+TXfmbpd6U7IpJ5jWhczoLGvF0NiCgKB6YHTaTGx4hoRLF2A744PzmQ==", "license": "Unlicense", "dependencies": { "@noble/ciphers": "^0.5.1", @@ -5426,6 +5430,8 @@ }, "node_modules/bch-dex-lib": { "version": "2.0.2", + "resolved": "http://94.130.170.209:4873/bch-dex-lib/-/bch-dex-lib-2.0.2.tgz", + "integrity": "sha512-x639TqYjRucvFrWzr9PMWN5Jdj2+lJkk1m4LC9Zlfm0QNe1PLD4WqyqH1dxMUdA/L9TVpPq1V/HbAiiQZmjV6Q==", "license": "MIT", "dependencies": { "@chris.troutner/retry-queue": "1.0.10", @@ -5500,6 +5506,8 @@ }, "node_modules/bch-nostr": { "version": "1.3.4", + "resolved": "http://94.130.170.209:4873/bch-nostr/-/bch-nostr-1.3.4.tgz", + "integrity": "sha512-5R6PtZsP1F+oDs7+sZ5d+DeM3XNhr+3QsyHmTbFdoyNcb3w0vFN3KYNvaXpfPYBUDncXPJBNSR5GOstEDRSCgg==", "license": "MIT", "dependencies": { "@chris.troutner/nostr-tools": "2.10.4", diff --git a/package.json b/package.json index 62d4cf4..3b64026 100644 --- a/package.json +++ b/package.json @@ -2,6 +2,7 @@ "name": "bch-dex-taker-v2", "version": "1.0.0", "dependencies": { + "@chris.troutner/nostr-tools": "2.10.4", "@chris.troutner/react-jdenticon": "1.0.1", "@fortawesome/fontawesome-svg-core": "6.7.2", "@fortawesome/free-regular-svg-icons": "6.7.2", @@ -10,6 +11,7 @@ "axios": "0.27.2", "bch-dex-lib": "2.0.2", "bch-message-lib": "2.2.1", + "bch-nostr": "1.3.4", "bootstrap": "5.2.0", "crypto-browserify": "3.12.1", "process": "0.11.10", diff --git a/src/App.js b/src/App.js index a505144..fea2703 100644 --- a/src/App.js +++ b/src/App.js @@ -71,6 +71,16 @@ function App (props) { addToModal('Getting BCH spot price in USD', appData) await asyncLoad.getUSDExchangeRate(walletTemp, appData.updateBchWalletState, appData) + // Load the DEX library. + addToModal('Loading DEX Library', appData) + const dexLib = await asyncLoad.getDexLib({bchWallet: walletTemp}) + appData.setDexLib(dexLib) + + // Load the Nostr library. + addToModal('Loading Nostr Library', appData) + const nostrLib = await asyncLoad.getNostrLib({bchWallet: walletTemp}) + appData.setNostr(nostrLib) + // Update state appData.setShowStartModal(false) appData.setDenyClose(false) diff --git a/src/components/app-body/nfts-for-sale/buy-button.js b/src/components/app-body/nfts-for-sale/buy-button.js new file mode 100644 index 0000000..bc49244 --- /dev/null +++ b/src/components/app-body/nfts-for-sale/buy-button.js @@ -0,0 +1,79 @@ +/* + This component renders as a button. When clicked, it initiates the + purchase of the token. This is the Signal part of the SWaP protocol. +*/ + +// Global npm libraries +import React, { useState } from 'react' +import { Button, Modal, Container, Row, Col } from 'react-bootstrap' + +function InfoButton (props) { + const [show, setShow] = useState(false) + + const handleClose = () => { + console.log('handleClose()') + setShow(false) + // props.instance.setState({ showModal: false }) + } + + const handleOpen = () => { + console.log('handleOpen()') + setShow(true) + } + + // Replace with dummy button until token data is loaded. + if (!props.token.tokenData) { + return ( + <> + + + ) + } + + return ( + <> + + + + Token Information + + + + + Ticker: + {props.token.ticker} + + + + Name: + {props.token.tokenData.genesisData.name} + + + + Token ID: + + + {props.token.tokenId} + + + + + + Decimals: + {props.token.tokenData.genesisData.decimals} + + + + Token Type: + {props.token.tokenType} + + + + + + + + ) +} + +export default InfoButton diff --git a/src/components/app-body/nfts-for-sale/index.js b/src/components/app-body/nfts-for-sale/index.js index 0d6328d..d5416b4 100644 --- a/src/components/app-body/nfts-for-sale/index.js +++ b/src/components/app-body/nfts-for-sale/index.js @@ -205,6 +205,7 @@ function NftsForSale (props) { } return url } + // This function generates a Token Card for each token in the wallet. function generateCards (offers) { console.log('generateCards() offerData: ', offers) diff --git a/src/components/app-body/nfts-for-sale/info-button.js b/src/components/app-body/nfts-for-sale/info-button.js index 271e9fb..9092771 100644 --- a/src/components/app-body/nfts-for-sale/info-button.js +++ b/src/components/app-body/nfts-for-sale/info-button.js @@ -54,7 +54,7 @@ function InfoButton (props) { Token ID: - + {props.token.tokenId} diff --git a/src/components/app-body/nfts-for-sale/token-card.js b/src/components/app-body/nfts-for-sale/token-card.js index 139f8f3..950b4ff 100644 --- a/src/components/app-body/nfts-for-sale/token-card.js +++ b/src/components/app-body/nfts-for-sale/token-card.js @@ -9,10 +9,12 @@ import InfoButton from './info-button' import Jdenticon from '@chris.troutner/react-jdenticon' function TokenCard (props) { - const { token } = props + const { token, appData } = props const [icon, setIcon] = useState(token.icon) const [tokenData, setTokenData] = useState(token.tokenData) + console.log('TokenCard() appData: ', appData) + // Update icon state every token.icon and token.tokenData changes useEffect(() => { setIcon(token.icon) diff --git a/src/hooks/state.js b/src/hooks/state.js index 1c9f835..61be583 100644 --- a/src/hooks/state.js +++ b/src/hooks/state.js @@ -23,6 +23,8 @@ function useAppState () { const [menuState, setMenuState] = useState(0) const [wallet, setWallet] = useState(false) const [servers, setServers] = useState([]) + const [dexLib, setDexLib] = useState(false) + const [nostr, setNostr] = useState(false) // Startup state management const [asyncInitStarted, setAsyncInitStarted] = useState(false) @@ -125,8 +127,11 @@ function useAppState () { updateLocalStorage, updateBchWalletState, appUtil: new AppUtil(), - currentPath: location.pathname - + currentPath: location.pathname, + dexLib, + setDexLib, + nostr, + setNostr } } diff --git a/src/services/async-load.js b/src/services/async-load.js index 03be3df..ae48ac2 100644 --- a/src/services/async-load.js +++ b/src/services/async-load.js @@ -4,8 +4,11 @@ // Global npm libraries import axios from 'axios' +import BchDexLib from 'bch-dex-lib' + // Local libraries import GistServers from './gist-servers' +import Nostr from './nostr' class AsyncLoad { constructor () { @@ -195,6 +198,34 @@ class AsyncLoad { return defaultOptions } } + + // Load the BchDexLib library. + async getDexLib (inObj = {}) { + try { + const { bchWallet } = inObj + + const dexLib = new BchDexLib({bchWallet, p2wdbRead: {}, p2wdbWrite: {}}) + + return dexLib + } catch (error) { + console.error('Error getting DexLib', error) + throw error + } + } + + // Load the Nostr library. + async getNostrLib (inObj = {}) { + try { + const { bchWallet } = inObj + + const nostrLib = new Nostr({bchWallet}) + + return nostrLib + } catch (error) { + console.error('Error in getNostrLib', error) + throw error + } + } } function sleep (ms) { diff --git a/src/services/nostr.js b/src/services/nostr.js new file mode 100644 index 0000000..d2443e7 --- /dev/null +++ b/src/services/nostr.js @@ -0,0 +1,110 @@ +/* + Implementing nostr natively in the browser, rather than using a library + intended for node.js +*/ + +// Global npm libraries +import { finalizeEvent } from '@chris.troutner/nostr-tools/pure' +import { Relay } from '@chris.troutner/nostr-tools/relay' +import BchNostr from 'bch-nostr' +import * as nip19 from '@chris.troutner/nostr-tools/nip19' + +class NostrBrowser { + constructor (localConfig = {}) { + if (!localConfig.bchWallet) { + throw new Error('Instance of minimal-slp-wallet must be passed as wallet property when instantiating the bch-dex-lib library.') + } + + this.bchWallet = localConfig.bchWallet + + this.bchNostr = new BchNostr({ + relayWs: 'wss://nostr-relay.psfoundation.info', + topic: 'bch-dex-test-topic-01' + }) + } + + async testNostrUpload (inObj = {}) { + try { + console.log('testNostrUpload() executing.') + // console.log('this.bchWallet: ', this.bchWallet) + + const { offerData, partialHex } = inObj + console.log('offerData: ', offerData) + + const wif = this.bchWallet.walletInfo.privateKey + // const { privKeyBuf, nostrPubKey } = + // this.bchNostr.keys.createNostrPubKeyFromWif({ wif }) + const { privKeyBuf } = + this.bchNostr.keys.createNostrPubKeyFromWif({ wif }) + + // Scaffold the Counter Offer from the Offer + const counterOfferData = Object.assign({}, offerData) + counterOfferData.partialTxHex = partialHex + delete counterOfferData.nostrEventId + delete counterOfferData._id + counterOfferData.offerHash = offerData.nostrEventId + + // Add P2WDB specific flag for signaling that this is a new Counter Offer. + counterOfferData.dataType = 'counter-offer' + console.log(`counterOfferData: ${JSON.stringify(counterOfferData, null, 2)}`) + + const nostrData = { + data: counterOfferData + } + + const msg = JSON.stringify(nostrData, null, 2) + + // const inObj = { + // kind: 867, + // privKeyBuf: privKeyBuf, + // nostrPubKey: nostrPubKey, + // relayWs: 'wss://nostr-relay.psfoundation.info', + // msg, + // tags: [['t', 'bch-dex-test-topic-01']] + // } + + const relayWs = 'wss://nostr-relay.psfoundation.info' + const eventTemplate = { + kind: 867, + created_at: Math.floor(Date.now() / 1000), + tags: [['t', 'bch-dex-test-topic-01']], + content: msg + } + + // Sign the post + const signedEvent = finalizeEvent(eventTemplate, privKeyBuf) + // console.log('signedEvent: ', signedEvent) + const eventId = signedEvent.id + + // Connect to a relay. + const relay = await Relay.connect(relayWs) + // console.log(`connected to ${relay.url}`) + + // Publish the message to the relay. + await relay.publish(signedEvent) + + // Close the connection to the relay. + relay.close() + + // const eventId = await this.bchNostr.post.uploadToNostr(inObj) + + const noteId = this.eventId2note(eventId) + + // const eventId = 'test1' + // const noteId = 'test2' + + return { eventId, noteId } + } catch (err) { + console.log('Error in nostr.js/testNostrUpload()') + throw err + } + } + + // Convert an Event ID into a `noteabc..` syntax that Astral expects. + // This can be used to generate a link to Astral to display the post. + eventId2note (eventId) { + return nip19.noteEncode(eventId) + } +} + +export default NostrBrowser