mirror of
https://github.com/Permissionless-Software-Foundation/bch-dex-taker-v2.git
synced 2026-09-21 16:52:01 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
aa2d92bfaf | ||
|
|
40467014f0 | ||
|
|
d94b3fc853 | ||
|
|
51ed5601fa | ||
|
|
d72d98a2d7 | ||
|
|
7237cb6c6a | ||
|
|
93c8eb42d3 | ||
|
|
fa3c71f0a1 |
Generated
+4
-4
@@ -15,7 +15,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",
|
||||||
@@ -10043,9 +10043,9 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/bch-dex-lib": {
|
"node_modules/bch-dex-lib": {
|
||||||
"version": "2.2.0",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/bch-dex-lib/-/bch-dex-lib-2.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/bch-dex-lib/-/bch-dex-lib-2.3.1.tgz",
|
||||||
"integrity": "sha512-SORqn4qNmbxfL2SbbapOpbcCB+FtqasR+GT0XRifdkXFjA3aDL0gwMsHjh8IzLZnKtPYSBcfgzKQFxEsXCJNug==",
|
"integrity": "sha512-HlY21BpUUaSsuZNWF5LGPwSJU7m2kYbNMcS36o22RSHuO1kiBMONHwkV+v22kdbDk3T/Iz+AZN0mzJvH5neCXw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@chris.troutner/retry-queue": "1.0.10",
|
"@chris.troutner/retry-queue": "1.0.10",
|
||||||
|
|||||||
+1
-1
@@ -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",
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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)
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,186 @@
|
|||||||
|
/*
|
||||||
|
This component displays the seller's profile information for an NFT listing.
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Global npm libraries
|
||||||
|
import React, { useState, useEffect } from 'react'
|
||||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||||
|
import { faUser } from '@fortawesome/free-solid-svg-icons'
|
||||||
|
|
||||||
|
function SellerProfile (props) {
|
||||||
|
const { npub, appData } = props
|
||||||
|
const [profile, setProfile] = useState(null)
|
||||||
|
const [imageError, setImageError] = useState(false)
|
||||||
|
const [isLoading, setIsLoading] = useState(true)
|
||||||
|
|
||||||
|
const { nostrQueries } = appData
|
||||||
|
|
||||||
|
// Get profile if npub is provided.
|
||||||
|
useEffect(() => {
|
||||||
|
const start = async () => {
|
||||||
|
setIsLoading(true)
|
||||||
|
try {
|
||||||
|
const pubKey = nostrQueries.npubToHex(npub)
|
||||||
|
const profile = await nostrQueries.getProfile(pubKey)
|
||||||
|
setProfile(profile)
|
||||||
|
} catch (error) {
|
||||||
|
console.warn('Error fetching seller profile:', error)
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (npub) {
|
||||||
|
start()
|
||||||
|
} else {
|
||||||
|
setIsLoading(false)
|
||||||
|
}
|
||||||
|
}, [npub, nostrQueries])
|
||||||
|
|
||||||
|
// handle img url errors
|
||||||
|
const handleImageError = (type) => {
|
||||||
|
setImageError(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
// go to profile
|
||||||
|
const goToProfile = () => {
|
||||||
|
if (npub) {
|
||||||
|
const profileUrl = `${window.location.origin}/profile/${npub}#single-view`
|
||||||
|
window.open(profileUrl, '_blank')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get display name - use profile name, npub short form, or "Anonymous User"
|
||||||
|
const getDisplayName = () => {
|
||||||
|
if (profile?.name) return profile.name
|
||||||
|
if (npub) {
|
||||||
|
return npub.slice(0, 8) + '...' + npub.slice(-6)
|
||||||
|
}
|
||||||
|
return 'Anonymous User'
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
onClick={goToProfile}
|
||||||
|
className='seller-profile-container'
|
||||||
|
style={{
|
||||||
|
padding: '4px 8px',
|
||||||
|
backgroundColor: '#f8f9fa',
|
||||||
|
borderRadius: '6px',
|
||||||
|
border: '1px solid #e9ecef',
|
||||||
|
transition: 'all 0.2s ease',
|
||||||
|
cursor: 'pointer',
|
||||||
|
display: 'flex',
|
||||||
|
justifyContent: 'center',
|
||||||
|
width: '100%'
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.backgroundColor = '#e9ecef'
|
||||||
|
e.currentTarget.style.borderColor = '#dee2e6'
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.backgroundColor = '#f8f9fa'
|
||||||
|
e.currentTarget.style.borderColor = '#e9ecef'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<div className='d-flex align-items-center justify-content-center gap-1'>
|
||||||
|
{/* Seller Label */}
|
||||||
|
<small
|
||||||
|
className='text-muted fw-semibold'
|
||||||
|
style={{
|
||||||
|
fontSize: '0.65rem',
|
||||||
|
textTransform: 'uppercase',
|
||||||
|
letterSpacing: '0.3px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
Seller:
|
||||||
|
</small>
|
||||||
|
|
||||||
|
{/* Profile Picture or Placeholder */}
|
||||||
|
<div
|
||||||
|
className='d-flex align-items-center justify-content-center'
|
||||||
|
style={{
|
||||||
|
width: '24px',
|
||||||
|
height: '24px',
|
||||||
|
borderRadius: '50%',
|
||||||
|
backgroundColor: '#e9ecef',
|
||||||
|
border: '1.5px solid #dee2e6',
|
||||||
|
overflow: 'hidden',
|
||||||
|
flexShrink: 0,
|
||||||
|
cursor: 'pointer',
|
||||||
|
transition: 'transform 0.2s ease'
|
||||||
|
}}
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
goToProfile()
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.transform = 'scale(1.1)'
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.transform = 'scale(1)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{isLoading
|
||||||
|
? (
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faUser}
|
||||||
|
style={{
|
||||||
|
color: '#adb5bd',
|
||||||
|
fontSize: '12px',
|
||||||
|
opacity: 0.5
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: (profile?.picture && !imageError)
|
||||||
|
? (
|
||||||
|
<img
|
||||||
|
src={profile.picture}
|
||||||
|
alt='Seller profile'
|
||||||
|
className='w-100 h-100'
|
||||||
|
style={{ objectFit: 'cover' }}
|
||||||
|
onError={() => handleImageError('picture')}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
: (
|
||||||
|
<FontAwesomeIcon
|
||||||
|
icon={faUser}
|
||||||
|
style={{
|
||||||
|
color: '#6c757d',
|
||||||
|
fontSize: '12px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Seller Name */}
|
||||||
|
<span
|
||||||
|
onClick={(e) => {
|
||||||
|
e.stopPropagation()
|
||||||
|
goToProfile()
|
||||||
|
}}
|
||||||
|
className='cursor-pointer fw-medium'
|
||||||
|
style={{
|
||||||
|
color: '#495057',
|
||||||
|
fontSize: '0.8rem',
|
||||||
|
transition: 'color 0.2s ease',
|
||||||
|
maxWidth: '120px',
|
||||||
|
overflow: 'hidden',
|
||||||
|
textOverflow: 'ellipsis',
|
||||||
|
whiteSpace: 'nowrap'
|
||||||
|
}}
|
||||||
|
onMouseEnter={(e) => {
|
||||||
|
e.currentTarget.style.color = '#007bff'
|
||||||
|
}}
|
||||||
|
onMouseLeave={(e) => {
|
||||||
|
e.currentTarget.style.color = '#495057'
|
||||||
|
}}
|
||||||
|
title={profile?.name || npub}
|
||||||
|
>
|
||||||
|
{getDisplayName()}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SellerProfile
|
||||||
@@ -10,14 +10,12 @@ import Jdenticon from '@chris.troutner/react-jdenticon'
|
|||||||
// Local libraries
|
// Local libraries
|
||||||
import InfoButton from './info-button'
|
import InfoButton from './info-button'
|
||||||
import BuyButton from './buy-button'
|
import BuyButton from './buy-button'
|
||||||
|
import SellerProfile from './seller-profile'
|
||||||
function TokenCard (props) {
|
function TokenCard (props) {
|
||||||
const { token, appData, handleRefresh, hideBuyBtn } = props
|
const { token, appData, handleRefresh, hideBuyBtn } = props
|
||||||
const [icon, setIcon] = useState(token.icon)
|
const [icon, setIcon] = useState(token.icon)
|
||||||
const [tokenData, setTokenData] = useState(token.tokenData)
|
const [tokenData, setTokenData] = useState(token.tokenData)
|
||||||
|
|
||||||
console.log('TokenCard() appData: ', appData)
|
|
||||||
|
|
||||||
// Update icon state every token.icon and token.tokenData changes
|
// Update icon state every token.icon and token.tokenData changes
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
console.log('setting icon')
|
console.log('setting icon')
|
||||||
@@ -28,8 +26,14 @@ function TokenCard (props) {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Col xs={12} sm={6} lg={4} style={{ padding: '25px' }}>
|
<Col xs={12} sm={6} lg={4} style={{ padding: '25px' }}>
|
||||||
<Card>
|
<Card className='shadow-sm'>
|
||||||
<Card.Body style={{ textAlign: 'center' }}>
|
<Card.Body style={{ textAlign: 'center', padding: '10px' }}>
|
||||||
|
{/* Seller Profile Section */}
|
||||||
|
<Row className='mb-2'>
|
||||||
|
<Col className='text-center mb-2'>
|
||||||
|
<SellerProfile npub={token.makerNpub} appData={appData} />
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
{/** If the icon is loaded, display it */
|
{/** If the icon is loaded, display it */
|
||||||
icon && (
|
icon && (
|
||||||
<Card.Img
|
<Card.Img
|
||||||
@@ -50,7 +54,6 @@ function TokenCard (props) {
|
|||||||
<Card.Title style={{ textAlign: 'center', marginTop: '10px' }}>
|
<Card.Title style={{ textAlign: 'center', marginTop: '10px' }}>
|
||||||
<h4>{token.ticker}</h4>
|
<h4>{token.ticker}</h4>
|
||||||
</Card.Title>
|
</Card.Title>
|
||||||
|
|
||||||
<Container>
|
<Container>
|
||||||
<Row>
|
<Row>
|
||||||
<Col>
|
<Col>
|
||||||
|
|||||||
@@ -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({
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
Reference in New Issue
Block a user