Compare commits

...
8 Commits
9 changed files with 1913 additions and 6446 deletions
+1654 -6441
View File
File diff suppressed because it is too large Load Diff
+13 -2
View File
@@ -9,7 +9,7 @@
"@fortawesome/react-fontawesome": "0.2.2", "@fortawesome/react-fontawesome": "0.2.2",
"@noble/hashes": "1.8.0", "@noble/hashes": "1.8.0",
"axios": "0.27.2", "axios": "0.27.2",
"bch-dex-lib": "2.2.0", "bch-dex-lib": "2.3.1",
"bch-message-lib": "2.2.1", "bch-message-lib": "2.2.1",
"bch-nostr": "1.3.4", "bch-nostr": "1.3.4",
"bch-token-sweep": "2.2.1", "bch-token-sweep": "2.2.1",
@@ -40,7 +40,8 @@
"eject": "react-app-rewired eject", "eject": "react-app-rewired eject",
"lint": "standard --env mocha --fix", "lint": "standard --env mocha --fix",
"pub": "node deploy/publish-main.js", "pub": "node deploy/publish-main.js",
"pub:ghp": "./deploy/publish-gh-pages.sh" "pub:ghp": "./deploy/publish-gh-pages.sh",
"postinstall": "rm -rf node_modules/fork-ts-checker-webpack-plugin/node_modules 2>/dev/null || true && (cd node_modules/@eslint/eslintrc && npm install ajv@^6.12.6 --no-save 2>/dev/null || true) && (cd node_modules/eslint && npm install ajv@^6.12.6 --no-save 2>/dev/null || true)"
}, },
"eslintConfig": { "eslintConfig": {
"extends": "react-app" "extends": "react-app"
@@ -66,6 +67,16 @@
"standard": "17.0.0", "standard": "17.0.0",
"web3.storage": "4.3.0" "web3.storage": "4.3.0"
}, },
"overrides": {
"ajv": "^8.17.1",
"ajv-keywords": "^5.1.0",
"ajv-formats": "^2.1.1",
"babel-loader": {
"schema-utils": "^2.7.1",
"ajv-keywords": "^3.5.2",
"ajv": "^6.12.6"
}
},
"release": { "release": {
"publish": [ "publish": [
{ {
@@ -0,0 +1,71 @@
/*
This Card component displays a counter offer with token icon, name, and price.
*/
// Global npm libraries
import React, { useState } from 'react'
import { Container, Row, Col, Card, Button } from 'react-bootstrap'
import Jdenticon from '@chris.troutner/react-jdenticon'
function CounterOfferCard (props) {
const { offer } = props
const [icon, setIcon] = useState(offer.tokenIcon)
return (
<>
<Col xs={12} sm={6} lg={4} style={{ padding: '25px' }}>
<Card className='shadow-sm'>
<Card.Body style={{ textAlign: 'center', padding: '10px' }}>
{/** If the icon is loaded, display it */}
{icon && (
<Card.Img
src={icon}
style={{ height: '100px', width: 'auto', margin: '0 auto' }}
onError={(e) => {
setIcon(null) // Set the icon to null if it fails to load the image url.
}}
/>
)}
{/** If the icon is not loaded, display the Jdenticon */}
{!icon && (
<div style={{ display: 'flex', justifyContent: 'center', marginBottom: '10px' }}>
<Jdenticon size='100' value={offer.tokenId} />
</div>
)}
<Card.Title style={{ textAlign: 'center', marginTop: '10px' }}>
<h4>{offer.ticker}</h4>
</Card.Title>
<Container>
<Row>
<Col>
{/* <strong>{offer.tokenName}</strong> */}
<strong>Counter Offer UTXO</strong>
</Col>
</Row>
<br />
<Row>
{/* <Col>Price:</Col> */}
<Col><strong>{offer.price}</strong></Col>
</Row>
<br />
<Row className='text-center'>
<Col>
<Button disabled variant='danger' size='sm'>
Cancel
</Button>
</Col>
</Row>
</Container>
</Card.Body>
</Card>
</Col>
</>
)
}
export default CounterOfferCard
@@ -0,0 +1,90 @@
/*
Shows Counter Offers created by the user.
*/
// Global npm libraries
import React, { useState, useEffect } from 'react'
import { Container, Row, Col, Spinner } from 'react-bootstrap'
// Local libraries
import CounterOfferCard from './counter-offer-card'
import AsyncLoad from '../../../services/async-load'
function CounterOffers (props) {
const appData = props.appData
const [counterOffers, setCounterOffers] = useState([])
const [isLoading, setIsLoading] = useState(true)
// Generate counter offer cards
const generateCards = () => {
return counterOffers.map((offer) => (
<CounterOfferCard
key={offer.id}
offer={offer}
appData={appData}
/>
))
}
useEffect(() => {
const loadWallet = async () => {
try {
setIsLoading(true)
const { bchWalletState, serverUrl } = appData
const asyncLoad = new AsyncLoad()
await asyncLoad.loadWalletLib()
const counterOfferWallet = await asyncLoad.getDerivatedWallet(serverUrl, bchWalletState.mnemonic, "m/44'/245'/0'/0/1")
const utxoStore = counterOfferWallet.utxos.utxoStore
const bchUtxos = utxoStore.bchUtxos
setCounterOffers(bchUtxos)
console.log('counterOffers', bchUtxos)
setIsLoading(false)
} catch (error) {
setIsLoading(false)
console.log('error', error)
}
}
loadWallet()
}, [appData])
return (
<Container>
<Row>
<Col>
<h1>Counter Offers</h1>
<p className='text-muted'>Your pending counter offers</p>
</Col>
</Row>
{isLoading
? (
<Row className='text-center' style={{ padding: '50px' }}>
<Col>
<Spinner animation='border' role='status'>
<span className='visually-hidden'>Loading...</span>
</Spinner>
<p className='mt-3 text-muted'>Loading counter offers...</p>
</Col>
</Row>
)
: (
<>
<Row>
{generateCards()}
</Row>
{counterOffers.length === 0 && (
<Row className='text-center'>
<Col>
<p>No counter offers found.</p>
</Col>
</Row>
)}
</>
)}
</Container>
)
}
export default CounterOffers
+2
View File
@@ -30,6 +30,7 @@ import ContentCreators from './nostr/content-creators/index.js'
import UserDataReview from './user-data-review' import UserDataReview from './user-data-review'
import Offers from './offers' import Offers from './offers'
import NostrChat from './nostr-chat' import NostrChat from './nostr-chat'
import CounterOffers from './counter-offers'
function AppBody (props) { function AppBody (props) {
// Dependency injection through props // Dependency injection through props
const appData = props.appData const appData = props.appData
@@ -55,6 +56,7 @@ function AppBody (props) {
<Route path='/content-creators' element={<ContentCreators appData={appData} />} /> <Route path='/content-creators' element={<ContentCreators appData={appData} />} />
<Route path='/user-data/:tokenId' element={<UserDataReview appData={appData} />} /> <Route path='/user-data/:tokenId' element={<UserDataReview appData={appData} />} />
<Route path='/offers' element={<Offers appData={appData} />} /> <Route path='/offers' element={<Offers appData={appData} />} />
<Route path='/counter-offers' element={<CounterOffers appData={appData} />} />
<Route path='/nostr-chat' element={<NostrChat appData={appData} />} /> <Route path='/nostr-chat' element={<NostrChat appData={appData} />} />
</Routes> </Routes>
{/** Show in all paths except the servers view */} {/** Show in all paths except the servers view */}
@@ -6,6 +6,7 @@
// Global npm libraries // Global npm libraries
import React, { useState } from 'react' import React, { useState } from 'react'
import { Button, Modal, Container, Row, Col, Spinner } from 'react-bootstrap' import { Button, Modal, Container, Row, Col, Spinner } from 'react-bootstrap'
import AsyncLoad from '../../../services/async-load'
function BuyButton (props) { function BuyButton (props) {
const { token, appData, onSuccess } = props const { token, appData, onSuccess } = props
@@ -45,10 +46,19 @@ function BuyButton (props) {
// Generate a counter offer. // Generate a counter offer.
const bchDexLib = appData.dexLib const bchDexLib = appData.dexLib
const { offerData, partialHex } = await bchDexLib.take.takeOffer( const { offerData, partialHex, counterOfferUtxo } = await bchDexLib.take.takeOffer(
targetOffer targetOffer
) )
// Get counter offer data
const asyncLoad = new AsyncLoad()
const { takerAddr, takerNpub, counterOfferAddr } = await asyncLoad.getCounterOfferMetadata(appData)
offerData.takerAddr = takerAddr
offerData.takerNpub = takerNpub
offerData.counterOfferAddr = counterOfferAddr
offerData.counterOfferUtxo = counterOfferUtxo.txid
progress.push(<p key='progress-msg2'>Uploading counter offer to Nostr...</p>) progress.push(<p key='progress-msg2'>Uploading counter offer to Nostr...</p>)
setProgressMsg(progress) setProgressMsg(progress)
+11 -2
View File
@@ -8,7 +8,7 @@ import React, { useState, useEffect, useCallback } from 'react'
import { Container, Row, Col, Table, Button, Spinner } from 'react-bootstrap' import { Container, Row, Col, Table, Button, Spinner } from 'react-bootstrap'
import axios from 'axios' import axios from 'axios'
import { DatatableWrapper, TableBody, TableHeader } from 'react-bs-datatable' import { DatatableWrapper, TableBody, TableHeader } from 'react-bs-datatable'
import AsyncLoad from '../../../services/async-load'
// Local libraries // Local libraries
import config from '../../../config' import config from '../../../config'
import WaitingModal from '../../waiting-modal' import WaitingModal from '../../waiting-modal'
@@ -100,10 +100,19 @@ function Offers (props) {
// Generate a counter offer. // Generate a counter offer.
const bchDexLib = appData.dexLib const bchDexLib = appData.dexLib
const { offerData, partialHex } = await bchDexLib.take.takeOffer( const { offerData, partialHex, counterOfferUtxo } = await bchDexLib.take.takeOffer(
targetOfferEventId targetOfferEventId
) )
// Get counter offer data
const asyncLoad = new AsyncLoad()
const { takerAddr, takerNpub, counterOfferAddr } = await asyncLoad.getCounterOfferMetadata(appData)
offerData.takerAddr = takerAddr
offerData.takerNpub = takerNpub
offerData.counterOfferAddr = counterOfferAddr
offerData.counterOfferUtxo = counterOfferUtxo.txid
// Upload the counter offer to Nostr. // Upload the counter offer to Nostr.
const nostr = appData.nostr const nostr = appData.nostr
const { eventId, noteId } = await nostr.testNostrUpload({ const { eventId, noteId } = await nostr.testNostrUpload({
+8
View File
@@ -58,6 +58,14 @@ function NavMenu (props) {
Fungible Tokens Fungible Tokens
</NavLink> </NavLink>
<NavLink
className={currentPath === '/counter-offers' ? 'nav-link-active' : 'nav-link-inactive'}
to='/counter-offers'
onClick={handleClickEvent}
>
Counter Offers
</NavLink>
<hr /> <hr />
<NavLink <NavLink
+53
View File
@@ -333,6 +333,59 @@ class AsyncLoad {
throw error throw error
} }
} }
async getDerivatedWallet (restURL, mnemonic, hdPath = "m/44'/245'/0'/0/0", initialize = true) {
try {
const options = {
interface: 'consumer-api',
restURL,
noUpdate: true,
hdPath
}
const wallet = new this.BchWallet(mnemonic, options)
// Wait for wallet to initialize.
await wallet.walletInfoPromise
if (initialize) {
await wallet.initialize()
}
return wallet
} catch (error) {
console.error('Error initStarterWallet: ', error)
throw error
}
}
// Get buyer and counter offer data.
async getCounterOfferMetadata (appData) {
try {
const { bchWalletState, serverUrl } = appData
// Start async load lib
const asyncLoad = new AsyncLoad()
await asyncLoad.loadWalletLib()
// Buyer wallet data
const buyerWallet = await asyncLoad.getDerivatedWallet(serverUrl, bchWalletState.mnemonic, "m/44'/245'/0'/0/0", false)
const buyerAddr = buyerWallet.walletInfo.cashAddress
const buyerKeyPair = asyncLoad.nostrKeyPairFromWIF(buyerWallet.walletInfo.privateKey)
const buyerNpub = buyerKeyPair.npub
// Counter offer wallet data
const counterOfferWallet = await asyncLoad.getDerivatedWallet(serverUrl, bchWalletState.mnemonic, "m/44'/245'/0'/0/1", false)
const counterOfferAddr = counterOfferWallet.walletInfo.cashAddress
return {
takerAddr: buyerAddr,
takerNpub: buyerNpub,
counterOfferAddr
}
} catch (error) {
console.error('Error getCounterOfferMetadata()', error)
throw error
}
}
} }
function sleep (ms) { function sleep (ms) {