mirror of
https://github.com/Permissionless-Software-Foundation/bch-dex.git
synced 2026-09-21 16:52:00 -07:00
Merge pull request #80 from Permissionless-Software-Foundation/dh-create-offr-nostr
feat(offer): Trigger createOffer() from Nostr topic
This commit is contained in:
Vendored
+1
-1
@@ -166,7 +166,7 @@ export default {
|
|||||||
chatPubSubChan: 'psf-ipfs-chat-001',
|
chatPubSubChan: 'psf-ipfs-chat-001',
|
||||||
|
|
||||||
// IPFS gateway
|
// IPFS gateway
|
||||||
ipfsGateway: process.env.IPFS_GATEWAY ? process.env.IPFS_GATEWAY : 'https://p2wdb-gateway-678.fullstack.cash/ipfs/',
|
ipfsGateway: process.env.IPFS_GATEWAY ? process.env.IPFS_GATEWAY : 'https://pin.fullstack.cash/ipfs/download/',
|
||||||
|
|
||||||
// This can add specific Circuit Relay v2 servers to connect to.
|
// This can add specific Circuit Relay v2 servers to connect to.
|
||||||
bootstrapRelays: [
|
bootstrapRelays: [
|
||||||
|
|||||||
@@ -34,7 +34,8 @@ const Offer = new mongoose.Schema({
|
|||||||
// SWaP Protocol Properties
|
// SWaP Protocol Properties
|
||||||
lokadId: { type: String },
|
lokadId: { type: String },
|
||||||
messageType: { type: Number },
|
messageType: { type: Number },
|
||||||
messageClass: { type: Number }
|
messageClass: { type: Number },
|
||||||
|
nostrEventId: { type: String } // Nostr Event Id.
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -4,8 +4,6 @@
|
|||||||
|
|
||||||
import wlogger from '../../../adapters/wlogger.js'
|
import wlogger from '../../../adapters/wlogger.js'
|
||||||
|
|
||||||
let _this
|
|
||||||
|
|
||||||
class OfferRESTControllerLib {
|
class OfferRESTControllerLib {
|
||||||
constructor (localConfig = {}) {
|
constructor (localConfig = {}) {
|
||||||
// Dependency Injection.
|
// Dependency Injection.
|
||||||
@@ -26,7 +24,13 @@ class OfferRESTControllerLib {
|
|||||||
this.OfferModel = this.adapters.localdb.Offer
|
this.OfferModel = this.adapters.localdb.Offer
|
||||||
// this.userUseCases = this.useCases.user
|
// this.userUseCases = this.useCases.user
|
||||||
|
|
||||||
_this = this
|
// Bind 'this' object to all subfunctions.
|
||||||
|
this.createOffer = this.createOffer.bind(this)
|
||||||
|
this.listOffers = this.listOffers.bind(this)
|
||||||
|
this.listNftOffers = this.listNftOffers.bind(this)
|
||||||
|
this.listFungibleOffers = this.listFungibleOffers.bind(this)
|
||||||
|
this.takeOffer = this.takeOffer.bind(this)
|
||||||
|
this.handleError = this.handleError.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
// No api-doc documentation because this wont be a public endpoint
|
// No api-doc documentation because this wont be a public endpoint
|
||||||
@@ -36,7 +40,7 @@ class OfferRESTControllerLib {
|
|||||||
|
|
||||||
const offerObj = ctx.request.body
|
const offerObj = ctx.request.body
|
||||||
|
|
||||||
await _this.useCases.offer.createOffer(offerObj)
|
await this.useCases.offer.createOffer(offerObj)
|
||||||
|
|
||||||
ctx.body = {
|
ctx.body = {
|
||||||
success: true
|
success: true
|
||||||
@@ -45,7 +49,7 @@ class OfferRESTControllerLib {
|
|||||||
// console.log(`err.message: ${err.message}`)
|
// console.log(`err.message: ${err.message}`)
|
||||||
// console.log('err: ', err)
|
// console.log('err: ', err)
|
||||||
// ctx.throw(422, err.message)
|
// ctx.throw(422, err.message)
|
||||||
_this.handleError(ctx, err)
|
this.handleError(ctx, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -55,12 +59,12 @@ class OfferRESTControllerLib {
|
|||||||
let page = ctx.params.page
|
let page = ctx.params.page
|
||||||
if (!page) page = 0
|
if (!page) page = 0
|
||||||
|
|
||||||
const offers = await _this.useCases.offer.listOffers(page)
|
const offers = await this.useCases.offer.listOffers(page)
|
||||||
|
|
||||||
ctx.body = offers
|
ctx.body = offers
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('Error in listOffers REST API handler: ', err)
|
console.log('Error in listOffers REST API handler: ', err)
|
||||||
_this.handleError(ctx, err)
|
this.handleError(ctx, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -70,12 +74,12 @@ class OfferRESTControllerLib {
|
|||||||
let page = ctx.params.page
|
let page = ctx.params.page
|
||||||
if (!page) page = 0
|
if (!page) page = 0
|
||||||
|
|
||||||
const offers = await _this.useCases.offer.listNftOffers(page)
|
const offers = await this.useCases.offer.listNftOffers(page)
|
||||||
|
|
||||||
ctx.body = offers
|
ctx.body = offers
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('Error in listNftOffers REST API handler: ', err)
|
console.log('Error in listNftOffers REST API handler: ', err)
|
||||||
_this.handleError(ctx, err)
|
this.handleError(ctx, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,12 +89,12 @@ class OfferRESTControllerLib {
|
|||||||
let page = ctx.params.page
|
let page = ctx.params.page
|
||||||
if (!page) page = 0
|
if (!page) page = 0
|
||||||
|
|
||||||
const offers = await _this.useCases.offer.listFungibleOffers(page)
|
const offers = await this.useCases.offer.listFungibleOffers(page)
|
||||||
|
|
||||||
ctx.body = offers
|
ctx.body = offers
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log('Error in Fungible REST API handler: ', err)
|
console.log('Error in Fungible REST API handler: ', err)
|
||||||
_this.handleError(ctx, err)
|
this.handleError(ctx, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -100,14 +104,14 @@ class OfferRESTControllerLib {
|
|||||||
try {
|
try {
|
||||||
console.log('REST API controller, body: ', ctx.request.body)
|
console.log('REST API controller, body: ', ctx.request.body)
|
||||||
|
|
||||||
const offerCid = ctx.request.body.offerCid
|
const nostrEventId = ctx.request.body.nostrEventId
|
||||||
|
|
||||||
const hash = await _this.useCases.offer.takeOffer(offerCid)
|
const eventId = await this.useCases.offer.takeOffer(nostrEventId)
|
||||||
|
|
||||||
ctx.body = { hash }
|
ctx.body = { eventId }
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
wlogger.error('Error in takeOffer() REST API handler.')
|
wlogger.error('Error in takeOffer() REST API handler.')
|
||||||
_this.handleError(ctx, err)
|
this.handleError(ctx, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -48,8 +48,13 @@ class OfferRouter {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 12/3/24 CT:
|
||||||
|
// Note: The createOffer() path was used by a P2WDB webhook to generate an
|
||||||
|
// Offer from and Order. This has been deprecated and Offers are now created
|
||||||
|
// by a Timer Controller monitoring a Nostr topic.
|
||||||
|
|
||||||
// Define the routes and attach the controller.
|
// Define the routes and attach the controller.
|
||||||
this.router.post('/', _this.offerRESTController.createOffer)
|
// this.router.post('/', _this.offerRESTController.createOffer) // Deprecated.
|
||||||
this.router.post('/take', _this.offerRESTController.takeOffer)
|
this.router.post('/take', _this.offerRESTController.takeOffer)
|
||||||
this.router.get('/list/all/:page', _this.offerRESTController.listOffers)
|
this.router.get('/list/all/:page', _this.offerRESTController.listOffers)
|
||||||
this.router.get('/list/nft/:page', _this.offerRESTController.listNftOffers)
|
this.router.get('/list/nft/:page', _this.offerRESTController.listNftOffers)
|
||||||
|
|||||||
@@ -29,13 +29,12 @@ class TimerControllers {
|
|||||||
this.gcOrders = this.gcOrders.bind(this)
|
this.gcOrders = this.gcOrders.bind(this)
|
||||||
this.gcOffers = this.gcOffers.bind(this)
|
this.gcOffers = this.gcOffers.bind(this)
|
||||||
this.checkDupOffers = this.checkDupOffers.bind(this)
|
this.checkDupOffers = this.checkDupOffers.bind(this)
|
||||||
|
this.loadOffers = this.loadOffers.bind(this)
|
||||||
// State
|
// State
|
||||||
this.gcOrdersInt = null
|
this.gcOrdersInt = null
|
||||||
this.gcOffersInt = null
|
this.gcOffersInt = null
|
||||||
this.checkDupOffersInt = null
|
this.checkDupOffersInt = null
|
||||||
|
this.loadOffersInt = null
|
||||||
this.startTimers()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start all the time-based controllers.
|
// Start all the time-based controllers.
|
||||||
@@ -43,6 +42,8 @@ class TimerControllers {
|
|||||||
this.gcOrdersInt = setInterval(this.gcOrders, 60000 * 5)
|
this.gcOrdersInt = setInterval(this.gcOrders, 60000 * 5)
|
||||||
this.gcOffersInt = setInterval(this.gcOffers, 60000 * 5)
|
this.gcOffersInt = setInterval(this.gcOffers, 60000 * 5)
|
||||||
this.checkDupOffersInt = setInterval(this.checkDupOffers, 60000 * 4.5)
|
this.checkDupOffersInt = setInterval(this.checkDupOffers, 60000 * 4.5)
|
||||||
|
this.loadOffersInt = setInterval(this.loadOffers, 60000 * 2)
|
||||||
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
stopTimers () {
|
stopTimers () {
|
||||||
@@ -55,9 +56,11 @@ class TimerControllers {
|
|||||||
gcOrders () {
|
gcOrders () {
|
||||||
try {
|
try {
|
||||||
this.useCases.order.removeStaleOrders()
|
this.useCases.order.removeStaleOrders()
|
||||||
|
return true
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Do not throw an error. This is a top-level function.
|
// Do not throw an error. This is a top-level function.
|
||||||
console.log('Error in timer-controllers.js/gcOrders(): ', err)
|
console.log('Error in timer-controllers.js/gcOrders(): ', err)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -65,9 +68,11 @@ class TimerControllers {
|
|||||||
gcOffers () {
|
gcOffers () {
|
||||||
try {
|
try {
|
||||||
this.useCases.offer.removeStaleOffers()
|
this.useCases.offer.removeStaleOffers()
|
||||||
|
return true
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Do not throw an error. This is a top-level function.
|
// Do not throw an error. This is a top-level function.
|
||||||
console.log('Error in timer-controllers.js/gcOffers(): ', err)
|
console.log('Error in timer-controllers.js/gcOffers(): ', err)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,9 +80,23 @@ class TimerControllers {
|
|||||||
checkDupOffers () {
|
checkDupOffers () {
|
||||||
try {
|
try {
|
||||||
this.useCases.offer.removeDuplicateOffers()
|
this.useCases.offer.removeDuplicateOffers()
|
||||||
|
return true
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Do not throw an error. This is a top-level function.
|
// Do not throw an error. This is a top-level function.
|
||||||
console.log('Error in timer-controllers.js/checkDupOffers(): ', err)
|
console.log('Error in timer-controllers.js/checkDupOffers(): ', err)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load offers From nostr .
|
||||||
|
async loadOffers () {
|
||||||
|
try {
|
||||||
|
await this.useCases.offer.loadOffers()
|
||||||
|
return true
|
||||||
|
} catch (err) {
|
||||||
|
// Do not throw an error. This is a top-level function.
|
||||||
|
console.log('Error in timer-controllers.js/loadOffers(): ', err)
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+137
-72
@@ -37,7 +37,7 @@ class OfferUseCases {
|
|||||||
this.orderUseCase = localConfig.order
|
this.orderUseCase = localConfig.order
|
||||||
if (!this.orderUseCase) {
|
if (!this.orderUseCase) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Instance of Offer Use Cases must be passed in when instantiating Offer Use Cases library.'
|
'Instance of Order Use Cases must be passed in when instantiating Offer Use Cases library.'
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -50,17 +50,17 @@ class OfferUseCases {
|
|||||||
|
|
||||||
// Bind 'this' object to functions
|
// Bind 'this' object to functions
|
||||||
this.detectNsfw = this.detectNsfw.bind(this)
|
this.detectNsfw = this.detectNsfw.bind(this)
|
||||||
|
this.loadOffers = this.loadOffers.bind(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
// This method is called by the POST /offer REST API controller, which is
|
// This method is called by timer controller to load offers from a Nostr topic.
|
||||||
// triggered by a P2WDB webhook.
|
|
||||||
async createOffer (offerObj) {
|
async createOffer (offerObj) {
|
||||||
try {
|
try {
|
||||||
console.log('Use Case createOffer(offerObj): ', offerObj)
|
console.log('Use Case createOffer(offerObj): ', offerObj)
|
||||||
|
|
||||||
// Return if Offer already exists in database with the same P2WDB CID.
|
// Return if Offer already exists in database with the same utxo transaction id.
|
||||||
try {
|
try {
|
||||||
await this.findOfferByHash(offerObj.hash)
|
await this.findOfferByTxid(offerObj.data.utxoTxid)
|
||||||
|
|
||||||
console.log('Offer already found in local database.')
|
console.log('Offer already found in local database.')
|
||||||
return false
|
return false
|
||||||
@@ -71,9 +71,9 @@ class OfferUseCases {
|
|||||||
// Input Validation
|
// Input Validation
|
||||||
// TODO: This is a hack. Find a better way to protect against the corner-
|
// TODO: This is a hack. Find a better way to protect against the corner-
|
||||||
// case of counter-offers getting routed here.
|
// case of counter-offers getting routed here.
|
||||||
if (offerObj.data.dataType === 'counter-offer') {
|
// if (offerObj.data.dataType === 'counter-offer') {
|
||||||
console.log('WARN: Counter Offer innappropriately routed to createOffer()')
|
// console.log('WARN: Counter Offer innappropriately routed to createOffer()')
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Verify that UTXO in offer is unspent. If it is spent, then ignore the
|
// Verify that UTXO in offer is unspent. If it is spent, then ignore the
|
||||||
// offer.
|
// offer.
|
||||||
@@ -90,6 +90,9 @@ class OfferUseCases {
|
|||||||
// A new offer gets a status of 'posted'
|
// A new offer gets a status of 'posted'
|
||||||
offerObj.data.offerStatus = 'posted'
|
offerObj.data.offerStatus = 'posted'
|
||||||
|
|
||||||
|
// Set timestamp
|
||||||
|
offerObj.timestamp = new Date().getTime()
|
||||||
|
|
||||||
const offerEntity = this.offerEntity.validate(offerObj)
|
const offerEntity = this.offerEntity.validate(offerObj)
|
||||||
console.log('offerEntity: ', offerEntity)
|
console.log('offerEntity: ', offerEntity)
|
||||||
|
|
||||||
@@ -117,7 +120,7 @@ class OfferUseCases {
|
|||||||
|
|
||||||
return true
|
return true
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error in createOffer()')
|
console.error('Error in createOffer()', err.message)
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -140,7 +143,7 @@ class OfferUseCases {
|
|||||||
// Retrieve the mutable data from Filecoin/IPFS.
|
// Retrieve the mutable data from Filecoin/IPFS.
|
||||||
// const url = `https://${cid}.ipfs.w3s.link/data.json`
|
// const url = `https://${cid}.ipfs.w3s.link/data.json`
|
||||||
const url = `${this.config.ipfsGateway}${cid}/data.json`
|
const url = `${this.config.ipfsGateway}${cid}/data.json`
|
||||||
const result = await axios.get(url)
|
const result = await this.axios.get(url)
|
||||||
const mutableData = result.data
|
const mutableData = result.data
|
||||||
console.log(`mutableData: ${JSON.stringify(mutableData, null, 2)}`)
|
console.log(`mutableData: ${JSON.stringify(mutableData, null, 2)}`)
|
||||||
|
|
||||||
@@ -207,11 +210,11 @@ class OfferUseCases {
|
|||||||
async listOffers (page = 0) {
|
async listOffers (page = 0) {
|
||||||
try {
|
try {
|
||||||
const data = await this.OfferModel.find({})
|
const data = await this.OfferModel.find({})
|
||||||
// Sort entries so newest entries show first.
|
// Sort entries so newest entries show first.
|
||||||
.sort('-timestamp')
|
.sort('-timestamp')
|
||||||
// Skip to the start of the selected page.
|
// Skip to the start of the selected page.
|
||||||
.skip(page * DEFAULT_ENTRIES_PER_PAGE)
|
.skip(page * DEFAULT_ENTRIES_PER_PAGE)
|
||||||
// Only return 20 results.
|
// Only return 20 results.
|
||||||
.limit(DEFAULT_ENTRIES_PER_PAGE)
|
.limit(DEFAULT_ENTRIES_PER_PAGE)
|
||||||
|
|
||||||
return data
|
return data
|
||||||
@@ -227,11 +230,11 @@ class OfferUseCases {
|
|||||||
displayCategory: { $ne: 'fungible' },
|
displayCategory: { $ne: 'fungible' },
|
||||||
nsfw
|
nsfw
|
||||||
})
|
})
|
||||||
// Sort entries so newest entries show first.
|
// Sort entries so newest entries show first.
|
||||||
.sort('-timestamp')
|
.sort('-timestamp')
|
||||||
// Skip to the start of the selected page.
|
// Skip to the start of the selected page.
|
||||||
.skip(page * NFT_ENTRIES_PER_PAGE)
|
.skip(page * NFT_ENTRIES_PER_PAGE)
|
||||||
// Only return 20 results.
|
// Only return 20 results.
|
||||||
.limit(NFT_ENTRIES_PER_PAGE)
|
.limit(NFT_ENTRIES_PER_PAGE)
|
||||||
|
|
||||||
// console.log('listNftOffers() returning this data: ', data)
|
// console.log('listNftOffers() returning this data: ', data)
|
||||||
@@ -246,11 +249,11 @@ class OfferUseCases {
|
|||||||
async listFungibleOffers (page = 0) {
|
async listFungibleOffers (page = 0) {
|
||||||
try {
|
try {
|
||||||
const data = await this.OfferModel.find({ displayCategory: 'fungible' })
|
const data = await this.OfferModel.find({ displayCategory: 'fungible' })
|
||||||
// Sort entries so newest entries show first.
|
// Sort entries so newest entries show first.
|
||||||
.sort('-timestamp')
|
.sort('-timestamp')
|
||||||
// Skip to the start of the selected page.
|
// Skip to the start of the selected page.
|
||||||
.skip(page * FUNGIBLE_ENTRIES_PER_PAGE)
|
.skip(page * FUNGIBLE_ENTRIES_PER_PAGE)
|
||||||
// Only return 20 results.
|
// Only return 20 results.
|
||||||
.limit(FUNGIBLE_ENTRIES_PER_PAGE)
|
.limit(FUNGIBLE_ENTRIES_PER_PAGE)
|
||||||
|
|
||||||
// console.log('listFungibleOffers() returning this data: ', data)
|
// console.log('listFungibleOffers() returning this data: ', data)
|
||||||
@@ -270,12 +273,12 @@ class OfferUseCases {
|
|||||||
// provide functionality to take less than the total amount of tokens offered
|
// provide functionality to take less than the total amount of tokens offered
|
||||||
// (offerInfo.numTokens). Taking less than the offered amount will be added
|
// (offerInfo.numTokens). Taking less than the offered amount will be added
|
||||||
// in the future.
|
// in the future.
|
||||||
async takeOffer (offerCid) {
|
async takeOffer (eventId) {
|
||||||
try {
|
try {
|
||||||
console.log('offerCid: ', offerCid)
|
if (!eventId || typeof eventId !== 'string') throw new Error('eventId must be a string')
|
||||||
|
|
||||||
// Get the Offer information
|
// Get the Offer information
|
||||||
const offerInfo = await this.findOfferByHash(offerCid)
|
const offerInfo = await this.findOfferByEvent(eventId)
|
||||||
console.log(`offerInfo: ${JSON.stringify(offerInfo, null, 2)}`)
|
console.log(`offerInfo: ${JSON.stringify(offerInfo, null, 2)}`)
|
||||||
|
|
||||||
// Ensure the offer is in a 'posted' state and not already 'taken'
|
// Ensure the offer is in a 'posted' state and not already 'taken'
|
||||||
@@ -288,6 +291,8 @@ class OfferUseCases {
|
|||||||
tx_hash: offerInfo.utxoTxid,
|
tx_hash: offerInfo.utxoTxid,
|
||||||
tx_pos: offerInfo.utxoVout
|
tx_pos: offerInfo.utxoVout
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Note : should be added to retry-queue?
|
||||||
const utxoStatus = await this.adapters.wallet.bchWallet.utxoIsValid(utxo)
|
const utxoStatus = await this.adapters.wallet.bchWallet.utxoIsValid(utxo)
|
||||||
console.log('utxoStatus: ', utxoStatus)
|
console.log('utxoStatus: ', utxoStatus)
|
||||||
if (!utxoStatus) {
|
if (!utxoStatus) {
|
||||||
@@ -307,6 +312,7 @@ class OfferUseCases {
|
|||||||
|
|
||||||
// Calculate amount of sats to generate a counter offer.
|
// Calculate amount of sats to generate a counter offer.
|
||||||
let satsToMove = Math.ceil(offerInfo.numTokens * parseInt(offerInfo.rateInBaseUnit))
|
let satsToMove = Math.ceil(offerInfo.numTokens * parseInt(offerInfo.rateInBaseUnit))
|
||||||
|
console.log('satsToMove', satsToMove, offerInfo)
|
||||||
if (isNaN(satsToMove)) {
|
if (isNaN(satsToMove)) {
|
||||||
throw new Error('Could not calculate the amount of BCH to generate counter offer')
|
throw new Error('Could not calculate the amount of BCH to generate counter offer')
|
||||||
}
|
}
|
||||||
@@ -332,9 +338,9 @@ class OfferUseCases {
|
|||||||
// Create valid Offer object
|
// Create valid Offer object
|
||||||
const takenOfferInfo = Object.assign({}, offerInfo)
|
const takenOfferInfo = Object.assign({}, offerInfo)
|
||||||
takenOfferInfo.partialTxHex = partialTxHex
|
takenOfferInfo.partialTxHex = partialTxHex
|
||||||
delete takenOfferInfo.p2wdbHash
|
delete takenOfferInfo.nostrEventId
|
||||||
delete takenOfferInfo._id
|
delete takenOfferInfo._id
|
||||||
takenOfferInfo.offerHash = offerInfo.p2wdbHash
|
takenOfferInfo.offerHash = offerInfo.nostrEventId
|
||||||
|
|
||||||
// Add P2WDB specific flag for signaling that this is a new offer.
|
// Add P2WDB specific flag for signaling that this is a new offer.
|
||||||
takenOfferInfo.dataType = 'counter-offer'
|
takenOfferInfo.dataType = 'counter-offer'
|
||||||
@@ -342,19 +348,19 @@ class OfferUseCases {
|
|||||||
// Write offer info to the P2WDB
|
// Write offer info to the P2WDB
|
||||||
// TODO: This will trigger the webhook. Find some way of triggering the
|
// TODO: This will trigger the webhook. Find some way of triggering the
|
||||||
// webhook on new offers, but not on counteroffers
|
// webhook on new offers, but not on counteroffers
|
||||||
const p2wdbObj = {
|
const nostrData = {
|
||||||
wif: this.adapters.wallet.bchWallet.walletInfo.privateKey,
|
wif: this.adapters.wallet.bchWallet.walletInfo.privateKey,
|
||||||
data: takenOfferInfo,
|
data: takenOfferInfo,
|
||||||
appId: this.config.p2wdbAppId
|
appId: this.config.p2wdbAppId
|
||||||
}
|
}
|
||||||
const hash = await this.adapters.p2wdb.write(p2wdbObj)
|
const resultEventId = await this.adapters.nostr.post(nostrData)
|
||||||
|
|
||||||
// Delete the Offer from the database, so that the user doesn't attempt
|
// Delete the Offer from the database, so that the user doesn't attempt
|
||||||
// to take the offer more than once.
|
// to take the offer more than once.
|
||||||
offerInfo.remove()
|
offerInfo.remove()
|
||||||
|
|
||||||
// Return the P2WDB CID
|
// Return the P2WDB CID
|
||||||
return hash
|
return resultEventId
|
||||||
|
|
||||||
// return 'fake-hash'
|
// return 'fake-hash'
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
@@ -377,9 +383,10 @@ class OfferUseCases {
|
|||||||
await this.adapters.wallet.bchWallet.initialize()
|
await this.adapters.wallet.bchWallet.initialize()
|
||||||
|
|
||||||
// Ensure the app wallet has enough funds to write to the P2WDB.
|
// Ensure the app wallet has enough funds to write to the P2WDB.
|
||||||
const wif = this.adapters.wallet.bchWallet.walletInfo.privateKey
|
// Note : this validation should be deprecated for nostr functionality?
|
||||||
const canWriteToP2WDB = await this.adapters.p2wdb.checkForSufficientFunds(wif)
|
// const wif = this.adapters.wallet.bchWallet.walletInfo.privateKey
|
||||||
if (!canWriteToP2WDB) throw new Error('App wallet does not have funds for writing to the P2WDB.')
|
// const canWriteToP2WDB = await this.adapters.p2wdb.checkForSufficientFunds(wif)
|
||||||
|
// if (!canWriteToP2WDB) throw new Error('App wallet does not have funds for writing to the P2WDB.')
|
||||||
|
|
||||||
if (offerEntity.buyOrSell.includes('sell')) {
|
if (offerEntity.buyOrSell.includes('sell')) {
|
||||||
// Sell Offer
|
// Sell Offer
|
||||||
@@ -398,7 +405,7 @@ class OfferUseCases {
|
|||||||
throw new Error('App wallet does not control enough BCH to purchase the tokens.')
|
throw new Error('App wallet does not control enough BCH to purchase the tokens.')
|
||||||
}
|
}
|
||||||
|
|
||||||
//
|
//
|
||||||
} else {
|
} else {
|
||||||
// Buy Offer
|
// Buy Offer
|
||||||
throw new Error('Buy offers are not supported yet.')
|
throw new Error('Buy offers are not supported yet.')
|
||||||
@@ -417,41 +424,66 @@ class OfferUseCases {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOfferByHash (p2wdbHash) {
|
// Retrieve an Order model from the database. Find it by its event Id.
|
||||||
// try {
|
async findOfferByEvent (nostrEventId) {
|
||||||
if (typeof p2wdbHash !== 'string' || !p2wdbHash) {
|
try {
|
||||||
throw new Error('p2wdbHash must be a string')
|
if (typeof nostrEventId !== 'string' || !nostrEventId) {
|
||||||
|
throw new Error('nostrEventId must be a string')
|
||||||
|
}
|
||||||
|
|
||||||
|
const order = await this.OfferModel.findOne({ nostrEventId })
|
||||||
|
|
||||||
|
if (!order) {
|
||||||
|
throw new Error('offer not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
const orderObject = order.toObject()
|
||||||
|
// return this.offerEntity.validateFromModel(offerObject)
|
||||||
|
|
||||||
|
return orderObject
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error in findOfferByEvent()')
|
||||||
|
throw err
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const offer = await this.OfferModel.findOne({ p2wdbHash })
|
async findOfferByTxid (utxoTxid) {
|
||||||
|
try {
|
||||||
|
// try {
|
||||||
|
if (typeof utxoTxid !== 'string' || !utxoTxid) {
|
||||||
|
throw new Error('utxoTxid must be a string')
|
||||||
|
}
|
||||||
|
|
||||||
if (!offer) {
|
const offer = await this.OfferModel.findOne({ utxoTxid })
|
||||||
throw new Error('offer not found')
|
|
||||||
|
// TODO: Offer should be found by TXID, then if there is more than one
|
||||||
|
// result, they should be filtered by the vout property. That will leave
|
||||||
|
// one remaining UTXO.
|
||||||
|
|
||||||
|
if (!offer) {
|
||||||
|
throw new Error('offer not found')
|
||||||
|
}
|
||||||
|
|
||||||
|
return offer
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in use-cases/offer/findOfferByTxid(): ', error.message)
|
||||||
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
return offer
|
|
||||||
|
|
||||||
// const offerObject = offer.toObject()
|
|
||||||
// return this.offerEntity.validateFromModel(offerObject)
|
|
||||||
|
|
||||||
// return offerObject
|
|
||||||
// } catch (err) {
|
|
||||||
// // console.error('Error in findOffer(): ', err)
|
|
||||||
// throw err
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This function is called by the P2WDB webhook REST API handler. When a
|
// This function is called by the P2WDB webhook REST API handler. When a
|
||||||
// Counter Offer is passed to bch-dex by the P2WDB, the data is then passed
|
// Counter Offer is passed to bch-dex by the P2WDB, the data is then passed
|
||||||
// to this function. It does due dilligence on the Counter Offer, then signs
|
// to this function. It does due dilligence on the Counter Offer, then signs
|
||||||
// and broadcasts the transaction to accept the Counter Offer.
|
// and broadcasts the transaction to accept the Counter Offer.
|
||||||
async acceptCounterOffer (p2wdbData) {
|
async acceptCounterOffer (offerData) {
|
||||||
try {
|
try {
|
||||||
console.log(`acceptCounterOffer() p2wdbData: ${JSON.stringify(p2wdbData, null, 2)}`)
|
console.log(`acceptCounterOffer() offerData: ${JSON.stringify(offerData, null, 2)}`)
|
||||||
|
|
||||||
// See if this instance of bch-dex is managing the Order associated with
|
// See if this instance of bch-dex is managing the Order associated with
|
||||||
// the incoming Counter Offer.
|
// the incoming Counter Offer.
|
||||||
const orderHash = p2wdbData.data.offerHash
|
|
||||||
|
// Note : this should be handle by nostrEvent id or UtxoId?
|
||||||
|
const orderHash = offerData.data.nostrEventId
|
||||||
let orderData = {}
|
let orderData = {}
|
||||||
try {
|
try {
|
||||||
orderData = await this.orderUseCase.findOrderByEvent(orderHash)
|
orderData = await this.orderUseCase.findOrderByEvent(orderHash)
|
||||||
@@ -462,12 +494,13 @@ class OfferUseCases {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Deserialize the partially signed transaction.
|
// Deserialize the partially signed transaction.
|
||||||
const txHex = p2wdbData.data.partialTxHex
|
const txHex = offerData.data.partialTxHex
|
||||||
const txObj = await this.adapters.wallet.deseralizeTx(txHex)
|
const txObj = await this.adapters.wallet.deseralizeTx(txHex)
|
||||||
console.log(`txObj: ${JSON.stringify(txObj, null, 2)}`)
|
console.log(`txObj: ${JSON.stringify(txObj, null, 2)}`)
|
||||||
|
|
||||||
// Ensure the 3rd output (vout=2) contains the required amount of BCH.
|
// Ensure the 3rd output (vout=2) contains the required amount of BCH.
|
||||||
const satsToReceive = Math.ceil(orderData.numTokens * parseInt(orderData.rateInBaseUnit))
|
const satsToReceive = Math.ceil(orderData.numTokens * parseInt(orderData.rateInBaseUnit))
|
||||||
|
console.log('Ceil', satsToReceive)
|
||||||
if (isNaN(satsToReceive)) {
|
if (isNaN(satsToReceive)) {
|
||||||
throw new Error('Could not calculate the amount of BCH offered in the Counter Offer')
|
throw new Error('Could not calculate the amount of BCH offered in the Counter Offer')
|
||||||
}
|
}
|
||||||
@@ -600,32 +633,64 @@ class OfferUseCases {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async flagOffer (flagData) {
|
// Flag offers as NSFW.
|
||||||
console.log(`flagData: ${JSON.stringify(flagData, null, 2)}`)
|
async flagOffer (flagData = {}) {
|
||||||
|
try {
|
||||||
|
if (!flagData.data) throw new Error('"data" property is required')
|
||||||
|
|
||||||
const p2wdbHash = flagData.data.p2wdbHash
|
console.log(`flagData: ${JSON.stringify(flagData, null, 2)}`)
|
||||||
|
|
||||||
// Get the offer from the database.
|
const eventId = flagData.data.nostrEventId
|
||||||
const offer = await this.findOfferByHash(p2wdbHash)
|
|
||||||
console.log(`Flagging this offer: ${JSON.stringify(offer, null, 2)}`)
|
|
||||||
|
|
||||||
if (!offer) {
|
// Get the offer from the database.
|
||||||
throw new Error(`Offer ${p2wdbHash} not found in the database.`)
|
const offer = await this.findOfferByEvent(eventId)
|
||||||
|
console.log(`Flagging this offer: ${JSON.stringify(offer, null, 2)}`)
|
||||||
|
|
||||||
|
if (!offer) {
|
||||||
|
throw new Error(`Offer ${eventId} not found in the database.`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add the raw flag data to the database model.
|
||||||
|
offer.flags.push(flagData)
|
||||||
|
|
||||||
|
// If flag count is 3 or more, mark the Offer as NSFW
|
||||||
|
const flagCnt = offer.flags.length
|
||||||
|
if (flagCnt >= 3) {
|
||||||
|
offer.nsfw = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save the updated offer data to the database.
|
||||||
|
await offer.save()
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in flagOffer(): ', error)
|
||||||
|
throw error
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add the raw flag data to the database model.
|
// Get offers data from nostr.
|
||||||
offer.flags.push(flagData)
|
async loadOffers () {
|
||||||
|
try {
|
||||||
|
// Retrieve offers array.
|
||||||
|
const offers = await this.adapters.nostr.read()
|
||||||
|
for (let i = 0; i < offers.length; i++) {
|
||||||
|
try {
|
||||||
|
const offer = offers[i]
|
||||||
|
|
||||||
// If flag count is 3 or more, mark the Offer as NSFW
|
// offer data
|
||||||
const flagCnt = offer.flags.length
|
const offerObj = JSON.parse(offer)
|
||||||
if (flagCnt >= 3) {
|
|
||||||
offer.nsfw = true
|
// Try to create new offer
|
||||||
|
await this.createOffer(offerObj)
|
||||||
|
} catch (error) {
|
||||||
|
/* exit quietly */
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error in loadOffers(): ', error)
|
||||||
|
throw error
|
||||||
}
|
}
|
||||||
|
|
||||||
// Save the updated offer data to the database.
|
|
||||||
await offer.save()
|
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class OrderLib {
|
|||||||
|
|
||||||
// Ensure sufficient tokens exist to create the order.
|
// Ensure sufficient tokens exist to create the order.
|
||||||
// await this.ensureFunds(orderEntity)
|
// await this.ensureFunds(orderEntity)
|
||||||
await this.retryQueue.addToQueue(this.ensureFunds, orderEntity)
|
// await this.retryQueue.addToQueue(this.ensureFunds, orderEntity)
|
||||||
|
|
||||||
// Get Ticker for token ID.
|
// Get Ticker for token ID.
|
||||||
// const tokenData = await this.adapters.wallet.bchWallet.getTxData([entryObj.tokenId])
|
// const tokenData = await this.adapters.wallet.bchWallet.getTxData([entryObj.tokenId])
|
||||||
|
|||||||
@@ -56,27 +56,70 @@ describe('#Timer-Controllers', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
// describe('#startTimers', () => {
|
describe('#startTimers', () => {
|
||||||
// it('should start the timers', () => {
|
it('should start the timers', () => {
|
||||||
// const result = uut.startTimers()
|
const result = uut.startTimers()
|
||||||
//
|
|
||||||
// // uut.stopTimers()
|
|
||||||
//
|
|
||||||
// assert.equal(result, true)
|
|
||||||
// })
|
|
||||||
// })
|
|
||||||
|
|
||||||
// describe('#exampleTimerFunc', () => {
|
// uut.stopTimers()
|
||||||
// it('should kick off the Use Case', async () => {
|
|
||||||
// const result = await uut.exampleTimerFunc()
|
assert.equal(result, true)
|
||||||
//
|
})
|
||||||
// assert.equal(result, true)
|
})
|
||||||
// })
|
|
||||||
//
|
describe('#gcOrders', () => {
|
||||||
// it('should return false on error', async () => {
|
it('should kick off the Use Case', async () => {
|
||||||
// const result = await uut.exampleTimerFunc(true)
|
const result = await uut.gcOrders()
|
||||||
//
|
|
||||||
// assert.equal(result, false)
|
assert.equal(result, true)
|
||||||
// })
|
})
|
||||||
// })
|
|
||||||
|
it('should return false on error', async () => {
|
||||||
|
sandbox.stub(uut.useCases.order, 'removeStaleOrders').throws(new Error('test error'))
|
||||||
|
const result = await uut.gcOrders()
|
||||||
|
|
||||||
|
assert.equal(result, false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('#gcOffers', () => {
|
||||||
|
it('should kick off the Use Case', async () => {
|
||||||
|
const result = await uut.gcOffers()
|
||||||
|
|
||||||
|
assert.equal(result, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return false on error', async () => {
|
||||||
|
sandbox.stub(uut.useCases.offer, 'removeStaleOffers').throws(new Error('test error'))
|
||||||
|
const result = await uut.gcOffers()
|
||||||
|
|
||||||
|
assert.equal(result, false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('#checkDupOffers', () => {
|
||||||
|
it('should kick off the Use Case', async () => {
|
||||||
|
const result = await uut.checkDupOffers()
|
||||||
|
|
||||||
|
assert.equal(result, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return false on error', async () => {
|
||||||
|
sandbox.stub(uut.useCases.offer, 'removeDuplicateOffers').throws(new Error('test error'))
|
||||||
|
const result = await uut.checkDupOffers()
|
||||||
|
|
||||||
|
assert.equal(result, false)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('#loadOffers', () => {
|
||||||
|
it('should kick off the Use Case', async () => {
|
||||||
|
const result = await uut.loadOffers()
|
||||||
|
|
||||||
|
assert.equal(result, true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return false on error', async () => {
|
||||||
|
sandbox.stub(uut.useCases.offer, 'loadOffers').throws(new Error('test error'))
|
||||||
|
const result = await uut.loadOffers()
|
||||||
|
|
||||||
|
assert.equal(result, false)
|
||||||
|
})
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -151,7 +151,11 @@ const wallet = {
|
|||||||
},
|
},
|
||||||
bchWallet: new MockBchWallet(),
|
bchWallet: new MockBchWallet(),
|
||||||
moveTokens: async () => {},
|
moveTokens: async () => {},
|
||||||
reclaimTokens: async ()=>{}
|
moveBch: async () => {},
|
||||||
|
reclaimTokens: async ()=>{},
|
||||||
|
generatePartialTx: async ()=>{},
|
||||||
|
deseralizeTx:async ()=>{},
|
||||||
|
completeTx:async ()=>{ return ''},
|
||||||
}
|
}
|
||||||
|
|
||||||
const p2wdb = {
|
const p2wdb = {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ class MockBchWallet {
|
|||||||
this.sendTokens = async () => {
|
this.sendTokens = async () => {
|
||||||
return 'fakeTxid';
|
return 'fakeTxid';
|
||||||
};
|
};
|
||||||
|
this.utxoIsValid =async ()=>{}
|
||||||
this.getUtxos = async () => { };
|
this.getUtxos = async () => { };
|
||||||
this.getBalance = async () => { };
|
this.getBalance = async () => { };
|
||||||
this.listTokens = async () => { };
|
this.listTokens = async () => { };
|
||||||
|
|||||||
@@ -41,12 +41,24 @@ class Offer {
|
|||||||
async createOffer() {
|
async createOffer() {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
async removeStaleOffers(){
|
||||||
|
|
||||||
|
}
|
||||||
|
async removeDuplicateOffers(){
|
||||||
|
|
||||||
|
}
|
||||||
|
async loadOffers(){
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class Order {
|
class Order {
|
||||||
async createOrder() {
|
async createOrder() {
|
||||||
return {}
|
return {}
|
||||||
}
|
}
|
||||||
|
async removeStaleOrders(){
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class UseCasesMock {
|
class UseCasesMock {
|
||||||
|
|||||||
@@ -3,45 +3,45 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
const nftOffer01 = {
|
const nftOffer01 = {
|
||||||
"messageType": 1,
|
"messageType": 1,
|
||||||
"messageClass": 1,
|
"messageClass": 1,
|
||||||
"tokenId": "eb93f05553ff088bffb0ec687519e83c59e5108c160f7c25a4b6c45109d7e40b",
|
"tokenId": "eb93f05553ff088bffb0ec687519e83c59e5108c160f7c25a4b6c45109d7e40b",
|
||||||
"buyOrSell": "sell",
|
"buyOrSell": "sell",
|
||||||
"rateInBaseUnit": 7672536,
|
"rateInBaseUnit": 7672536,
|
||||||
"minUnitsToExchange": 7672536,
|
"minUnitsToExchange": 7672536,
|
||||||
"numTokens": 1,
|
"numTokens": 1,
|
||||||
"utxoTxid": "c736d73e274df20e9b069b4990d1a264fb80aa98d67cc6c7a39e42bff48e7c04",
|
"utxoTxid": "c736d73e274df20e9b069b4990d1a264fb80aa98d67cc6c7a39e42bff48e7c04",
|
||||||
"utxoVout": 1,
|
"utxoVout": 1,
|
||||||
"timestamp": 1662930309998,
|
"timestamp": 1662930309998,
|
||||||
"globaltimestamp": "2022-09-11T21:05:09.998Z",
|
"globaltimestamp": "2022-09-11T21:05:09.998Z",
|
||||||
"localTimestamp": "9/11/2022, 9:05:09 PM",
|
"localTimestamp": "9/11/2022, 9:05:09 PM",
|
||||||
"txid": "4266862b8358664996038d8c29d49dc0f3d3058fd1ef3d567b084e6b16ceb5b2",
|
"txid": "4266862b8358664996038d8c29d49dc0f3d3058fd1ef3d567b084e6b16ceb5b2",
|
||||||
"p2wdbHash": "zdpuArq7rCuVCGPyTnWpLjy9AYsh8yrbwjGdKwg2GDDALkM8t",
|
"p2wdbHash": "zdpuArq7rCuVCGPyTnWpLjy9AYsh8yrbwjGdKwg2GDDALkM8t",
|
||||||
"offerStatus": "posted",
|
"offerStatus": "posted",
|
||||||
"makerAddr": "bitcoincash:qrqlz63cwmu0hcmsrfnd8jemn3atkpaqds6tf4ksrr",
|
"makerAddr": "bitcoincash:qrqlz63cwmu0hcmsrfnd8jemn3atkpaqds6tf4ksrr",
|
||||||
"ticker": "TV001",
|
"ticker": "TV001",
|
||||||
"tokenType": 65
|
"tokenType": 65
|
||||||
}
|
}
|
||||||
|
|
||||||
const nftTokenData01 = {
|
const nftTokenData01 = {
|
||||||
"genesisData": {
|
"genesisData": {
|
||||||
"type": 65,
|
"type": 65,
|
||||||
"ticker": "TV001",
|
"ticker": "TV001",
|
||||||
"name": "Introduction to NFTs on BCH",
|
"name": "Introduction to NFTs on BCH",
|
||||||
"tokenId": "eb93f05553ff088bffb0ec687519e83c59e5108c160f7c25a4b6c45109d7e40b",
|
"tokenId": "eb93f05553ff088bffb0ec687519e83c59e5108c160f7c25a4b6c45109d7e40b",
|
||||||
"documentUri": "ipfs://bafybeibmgilu4lk3uivwyzhtm7jorl5zbsluewqmcsxs6otzrphh33okk4",
|
"documentUri": "ipfs://bafybeibmgilu4lk3uivwyzhtm7jorl5zbsluewqmcsxs6otzrphh33okk4",
|
||||||
"documentHash": "c1731268f4873f1928438abdaf6ffc546d86a1817dc6f3c6bc73fbdfb4664f10",
|
"documentHash": "c1731268f4873f1928438abdaf6ffc546d86a1817dc6f3c6bc73fbdfb4664f10",
|
||||||
"decimals": 0,
|
"decimals": 0,
|
||||||
"mintBatonIsActive": false,
|
"mintBatonIsActive": false,
|
||||||
"tokensInCirculationBN": "1",
|
"tokensInCirculationBN": "1",
|
||||||
"tokensInCirculationStr": "1",
|
"tokensInCirculationStr": "1",
|
||||||
"blockCreated": 740395,
|
"blockCreated": 740395,
|
||||||
"totalBurned": "0",
|
"totalBurned": "0",
|
||||||
"totalMinted": "1",
|
"totalMinted": "1",
|
||||||
"parentGroupId": "030563ddd65772d8e9b79b825529ed53c7d27037507b57c528788612b4911107"
|
"parentGroupId": "030563ddd65772d8e9b79b825529ed53c7d27037507b57c528788612b4911107"
|
||||||
},
|
},
|
||||||
"immutableData": "ipfs://bafybeibmgilu4lk3uivwyzhtm7jorl5zbsluewqmcsxs6otzrphh33okk4",
|
"immutableData": "ipfs://bafybeibmgilu4lk3uivwyzhtm7jorl5zbsluewqmcsxs6otzrphh33okk4",
|
||||||
"mutableData": "ipfs://bafybeifzhunfpodsztsj5x4ypopngkxuapbxwaxaxkahaybdktk6joqtlq"
|
"mutableData": "ipfs://bafybeifzhunfpodsztsj5x4ypopngkxuapbxwaxaxkahaybdktk6joqtlq"
|
||||||
}
|
}
|
||||||
|
|
||||||
const simpleNftOffer01 = {
|
const simpleNftOffer01 = {
|
||||||
@@ -126,11 +126,56 @@ const fungibleTokenData01 = {
|
|||||||
"mutableData": ""
|
"mutableData": ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const offerMockData = {
|
||||||
|
data: {
|
||||||
|
messageType: 1,
|
||||||
|
messageClass: 1,
|
||||||
|
tokenId:
|
||||||
|
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
|
||||||
|
buyOrSell: 'sell',
|
||||||
|
rateInBaseUnit: 1000,
|
||||||
|
minUnitsToExchange: 10,
|
||||||
|
numTokens: 0.02,
|
||||||
|
utxoTxid:
|
||||||
|
'241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
|
||||||
|
utxoVout: 0,
|
||||||
|
makerAddr: 'address',
|
||||||
|
tokenType: 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const deserealizeTxMock = {
|
||||||
|
//...
|
||||||
|
vout: [
|
||||||
|
{
|
||||||
|
value: 0,
|
||||||
|
scriptPubKey: {
|
||||||
|
addresses: ['bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7478']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 0,
|
||||||
|
scriptPubKey: {
|
||||||
|
addresses: ['bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7478']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 0,
|
||||||
|
scriptPubKey: {
|
||||||
|
addresses: ['bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7478']
|
||||||
|
}
|
||||||
|
},
|
||||||
|
//...
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
nftOffer01,
|
nftOffer01,
|
||||||
nftTokenData01,
|
nftTokenData01,
|
||||||
simpleNftOffer01,
|
simpleNftOffer01,
|
||||||
simpleNftTokenData01,
|
simpleNftTokenData01,
|
||||||
fungibleOffer01,
|
fungibleOffer01,
|
||||||
fungibleTokenData01
|
fungibleTokenData01,
|
||||||
|
offerMockData,
|
||||||
|
deserealizeTxMock
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -48,79 +48,67 @@ describe('#offer-use-case', () => {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
it('should throw an error if order use cases are not passed in', () => {
|
||||||
|
try {
|
||||||
|
uut = new OfferLib({ adapters })
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
console.log(uut) // linter
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(
|
||||||
|
err.message,
|
||||||
|
'Instance of Order Use Cases must be passed in when instantiating Offer Use Cases library.'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#createOffer', () => {
|
describe('#createOffer', () => {
|
||||||
// it('should ignore an offer if utxo has been spent', async () => {
|
it('should handle error', async () => {
|
||||||
// const offerObj = {
|
try {
|
||||||
// appId: 'swapTest555',
|
await uut.createOffer()
|
||||||
// data: {
|
assert.fail('unexpected code path')
|
||||||
// messageType: 1,
|
} catch (error) {
|
||||||
// messageClass: 1,
|
assert.include(error.message, 'Cannot read properties of undefined')
|
||||||
// tokenId:
|
}
|
||||||
// '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
|
})
|
||||||
// buyOrSell: 'sell',
|
it('should return false if offer already exist', async () => {
|
||||||
// rateInSats: 1000,
|
const offerObj = mockData.offerMockData
|
||||||
// minSatsToExchange: 10,
|
|
||||||
// numTokens: 0.02,
|
|
||||||
// utxoTxid:
|
|
||||||
// '241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
|
|
||||||
// utxoVout: 0
|
|
||||||
// },
|
|
||||||
// timestamp: '2021-09-20T17:54:26.395Z',
|
|
||||||
// localTimeStamp: '9/20/2021, 10:54:26 AM',
|
|
||||||
// txid: '46f50f2a0cf44e3ed70dfb0618ef3ebfee57aabcf229b5d2d17c07322b54a8d7',
|
|
||||||
// hash: 'zdpuB2X25AZCKo3wpr4sSbw44vqPWJRqcxWQRHZccK5BdtoGD'
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// // Mock dependencies
|
|
||||||
// // sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').resolves(false)
|
|
||||||
// sandbox.stub(uut.adapters.wallet.bchWallet, 'getTokenData').resolves({})
|
|
||||||
// sandbox.stub(uut, 'categorizeToken').resolves('nft')
|
|
||||||
// sandbox.stub(uut, 'detectNsfw').resolves(false)
|
|
||||||
//
|
|
||||||
// const result = await uut.createOffer(offerObj)
|
|
||||||
// // console.log('result: ', result)
|
|
||||||
//
|
|
||||||
// assert.equal(result, false)
|
|
||||||
// })
|
|
||||||
|
|
||||||
// it('should create an offer and return the hash', async () => {
|
// Mock dependencies
|
||||||
// const offerObj = {
|
// sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').resolves(false)
|
||||||
// appId: 'swapTest555',
|
sandbox.stub(uut, 'findOfferByTxid').resolves({})
|
||||||
// data: {
|
|
||||||
// messageType: 1,
|
const result = await uut.createOffer(offerObj)
|
||||||
// messageClass: 1,
|
assert.isFalse(result)
|
||||||
// tokenId:
|
})
|
||||||
// '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
|
it('should return false for invalid utxo', async () => {
|
||||||
// buyOrSell: 'sell',
|
const offerObj = mockData.offerMockData
|
||||||
// rateInBaseUnit: 1000,
|
|
||||||
// minUnitsToExchange: 10,
|
// Mock dependencies
|
||||||
// numTokens: 0.02,
|
// sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').resolves(false)
|
||||||
// utxoTxid:
|
sandbox.stub(uut, 'findOfferByTxid').throws(new Error('offer not found'))
|
||||||
// '241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
|
sandbox.stub(uut.retryQueue, 'addToQueue').resolves(null)
|
||||||
// utxoVout: 0,
|
|
||||||
// makerAddr: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
|
const result = await uut.createOffer(offerObj)
|
||||||
// ticker: 'TROUT',
|
assert.isFalse(result)
|
||||||
// tokenType: 1
|
})
|
||||||
// },
|
|
||||||
// timestamp: '2021-09-20T17:54:26.395Z',
|
it('should create offer', async () => {
|
||||||
// localTimeStamp: '9/20/2021, 10:54:26 AM',
|
const tokenDataMock = mockData.simpleNftTokenData01
|
||||||
// txid: '46f50f2a0cf44e3ed70dfb0618ef3ebfee57aabcf229b5d2d17c07322b54a8d7',
|
const offerObj = mockData.offerMockData
|
||||||
// hash: 'zdpuB2X25AZCKo3wpr4sSbw44vqPWJRqcxWQRHZccK5BdtoGD'
|
|
||||||
// }
|
// Mock dependencies
|
||||||
//
|
// sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').resolves(false)
|
||||||
// // Mock dependencies
|
sandbox.stub(uut, 'findOfferByTxid').throws(new Error('offer not found'))
|
||||||
// // sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').resolves(true)
|
sandbox.stub(uut.retryQueue, 'addToQueue')
|
||||||
// sandbox.stub(uut, 'categorizeToken').resolves('fungible')
|
.onCall(0).resolves({}) // Utxo Status call
|
||||||
// sandbox.stub(uut.adapters.wallet.bchWallet, 'getTokenData').resolves({})
|
.onCall(1).resolves(tokenDataMock) // Token Data call
|
||||||
// sandbox.stub(uut, 'detectNsfw').resolves(false)
|
.onCall(2).resolves(false) // detectNsfw call
|
||||||
//
|
|
||||||
// const result = await uut.createOffer(offerObj)
|
const result = await uut.createOffer(offerObj)
|
||||||
// // console.log('result: ', result)
|
assert.isTrue(result)
|
||||||
//
|
})
|
||||||
// assert.equal(result, true)
|
|
||||||
// })
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#categorizeToken', () => {
|
describe('#categorizeToken', () => {
|
||||||
@@ -159,15 +147,28 @@ describe('#offer-use-case', () => {
|
|||||||
|
|
||||||
assert.equal(result, 'fungible')
|
assert.equal(result, 'fungible')
|
||||||
})
|
})
|
||||||
|
it('should unknow type', async () => {
|
||||||
|
try {
|
||||||
|
// Mock dependencies
|
||||||
|
const offerData = mockData.fungibleOffer01
|
||||||
|
const tokenData = { genesisData: {} }
|
||||||
|
|
||||||
|
await uut.categorizeToken(offerData, tokenData)
|
||||||
|
|
||||||
|
assert.fail('unexpected code path')
|
||||||
|
} catch (error) {
|
||||||
|
assert.include(error.message, 'Unknown token type:')
|
||||||
|
}
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#removeDuplicateOffers', () => {
|
describe('#removeDuplicateOffers', () => {
|
||||||
it('should remove duplicate entries and return true', async () => {
|
it('should remove duplicate entries and return true', async () => {
|
||||||
// Mock dependencies and force desired code path.
|
// Mock dependencies and force desired code path.
|
||||||
sandbox.stub(uut.OfferModel, 'find').resolves([
|
sandbox.stub(uut.OfferModel, 'find').resolves([
|
||||||
{ p2wdbHash: 'a', remove: async () => {} },
|
{ p2wdbHash: 'a', remove: async () => { } },
|
||||||
{ p2wdbHash: 'a', remove: async () => {} },
|
{ p2wdbHash: 'a', remove: async () => { } },
|
||||||
{ p2wdbHash: 'b', remove: async () => {} }
|
{ p2wdbHash: 'b', remove: async () => { } }
|
||||||
])
|
])
|
||||||
// sandbox.stub(uut.OfferModel, 'remove').resolves()
|
// sandbox.stub(uut.OfferModel, 'remove').resolves()
|
||||||
|
|
||||||
@@ -180,8 +181,8 @@ describe('#offer-use-case', () => {
|
|||||||
it('should return false if there are no duplicate entries', async () => {
|
it('should return false if there are no duplicate entries', async () => {
|
||||||
// Mock dependencies and force desired code path.
|
// Mock dependencies and force desired code path.
|
||||||
sandbox.stub(uut.OfferModel, 'find').resolves([
|
sandbox.stub(uut.OfferModel, 'find').resolves([
|
||||||
{ p2wdbHash: 'a', remove: async () => {} },
|
{ p2wdbHash: 'a', remove: async () => { } },
|
||||||
{ p2wdbHash: 'b', remove: async () => {} }
|
{ p2wdbHash: 'b', remove: async () => { } }
|
||||||
])
|
])
|
||||||
// sandbox.stub(uut.OfferModel, 'remove').resolves()
|
// sandbox.stub(uut.OfferModel, 'remove').resolves()
|
||||||
|
|
||||||
@@ -190,5 +191,554 @@ describe('#offer-use-case', () => {
|
|||||||
|
|
||||||
assert.equal(result, false)
|
assert.equal(result, false)
|
||||||
})
|
})
|
||||||
|
it('should handle error', async () => {
|
||||||
|
try {
|
||||||
|
// Mock dependencies and force desired code path.
|
||||||
|
sandbox.stub(uut.OfferModel, 'find').throws(new Error('test error'))
|
||||||
|
// sandbox.stub(uut.OfferModel, 'remove').resolves()
|
||||||
|
|
||||||
|
await uut.removeDuplicateOffers()
|
||||||
|
assert.fail('unexpected code path')
|
||||||
|
} catch (error) {
|
||||||
|
assert.equal(error.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('#removeStaleOffers', () => {
|
||||||
|
it('remove offer with wrong utxoState', async () => {
|
||||||
|
// Mock dependencies and force desired code path.
|
||||||
|
sandbox.stub(uut.OfferModel, 'find').resolves([{ remove: async () => { } }])
|
||||||
|
sandbox.stub(uut.retryQueue, 'addToQueue').resolves(false)
|
||||||
|
|
||||||
|
await uut.removeStaleOffers()
|
||||||
|
})
|
||||||
|
it('remove offer with wrong txid', async () => {
|
||||||
|
// Mock dependencies and force desired code path.
|
||||||
|
sandbox.stub(uut.OfferModel, 'find').resolves([{ remove: async () => { } }])
|
||||||
|
sandbox.stub(uut.retryQueue, 'addToQueue').throws(new Error('txid needs to be a proper transaction ID'))
|
||||||
|
|
||||||
|
await uut.removeStaleOffers()
|
||||||
|
})
|
||||||
|
it('remove expired offer ', async () => {
|
||||||
|
const tsMock = new Date()
|
||||||
|
tsMock.setMonth(tsMock.getMonth() - 3)
|
||||||
|
|
||||||
|
const timestamp = tsMock.getTime()
|
||||||
|
// Mock dependencies and force desired code path.
|
||||||
|
sandbox.stub(uut.OfferModel, 'find').resolves([{ timestamp, remove: async () => { } }])
|
||||||
|
sandbox.stub(uut.retryQueue, 'addToQueue').resolves(true)
|
||||||
|
|
||||||
|
await uut.removeStaleOffers()
|
||||||
|
})
|
||||||
|
it('should handle axios error ', async () => {
|
||||||
|
const testErr = new Error()
|
||||||
|
testErr.isAxiosError = true
|
||||||
|
|
||||||
|
// Mock dependencies and force desired code path.
|
||||||
|
sandbox.stub(uut.OfferModel, 'find').resolves([{ remove: async () => { } }])
|
||||||
|
sandbox.stub(uut.retryQueue, 'addToQueue').throws(testErr)
|
||||||
|
|
||||||
|
await uut.removeStaleOffers()
|
||||||
|
})
|
||||||
|
it('should handle error ', async () => {
|
||||||
|
try {
|
||||||
|
const testErr = new Error('unknow error')
|
||||||
|
|
||||||
|
// Mock dependencies and force desired code path.
|
||||||
|
sandbox.stub(uut.OfferModel, 'find').resolves([{ remove: async () => { } }])
|
||||||
|
sandbox.stub(uut.retryQueue, 'addToQueue').throws(testErr)
|
||||||
|
|
||||||
|
await uut.removeStaleOffers()
|
||||||
|
assert.fail('unexpected code path')
|
||||||
|
} catch (error) {
|
||||||
|
assert.equal(error.message, 'unknow error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('#findOfferByTxid', () => {
|
||||||
|
it('should throw an error if input is not provided', async () => {
|
||||||
|
try {
|
||||||
|
await uut.findOfferByTxid()
|
||||||
|
assert.fail('unexpected code path')
|
||||||
|
} catch (error) {
|
||||||
|
assert.equal(error.message, 'utxoTxid must be a string')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should throw an error if offer is not found', async () => {
|
||||||
|
try {
|
||||||
|
// Mock dependencies and force desired code path.
|
||||||
|
sandbox.stub(uut.OfferModel, 'findOne').resolves(null)
|
||||||
|
|
||||||
|
await uut.findOfferByTxid('241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87')
|
||||||
|
assert.fail('unexpected code path')
|
||||||
|
} catch (error) {
|
||||||
|
assert.equal(error.message, 'offer not found')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return offer', async () => {
|
||||||
|
// Mock dependencies and force desired code path.
|
||||||
|
sandbox.stub(uut.OfferModel, 'findOne').resolves(mockData.nftOffer01)
|
||||||
|
|
||||||
|
const result = await uut.findOfferByTxid('241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87')
|
||||||
|
assert.isObject(result)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('#detectNsfw', () => {
|
||||||
|
it('should return false for wrong cid format', async () => {
|
||||||
|
const result = await uut.detectNsfw({ mutableData: '' })
|
||||||
|
assert.isFalse(result)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should return true if nft boolean detected', async () => {
|
||||||
|
// Mock dependencies and force desired code path.
|
||||||
|
sandbox.stub(uut.axios, 'get').resolves({ data: { nsfw: true } })
|
||||||
|
|
||||||
|
const result = await uut.detectNsfw({ mutableData: 'ipfs://bafybeibqnsmmh6bkf2wwextetki4tly65z4r4qkrrpl5xwgvzdzjley6wm' })
|
||||||
|
assert.isTrue(result)
|
||||||
|
})
|
||||||
|
it('should return true if nft string detected', async () => {
|
||||||
|
// Mock dependencies and force desired code path.
|
||||||
|
sandbox.stub(uut.axios, 'get').resolves({ data: { nsfw: 'true' } })
|
||||||
|
|
||||||
|
const result = await uut.detectNsfw({ mutableData: 'ipfs://bafybeibqnsmmh6bkf2wwextetki4tly65z4r4qkrrpl5xwgvzdzjley6wm' })
|
||||||
|
assert.isTrue(result)
|
||||||
|
})
|
||||||
|
it('should return false if nfsw property does not exist', async () => {
|
||||||
|
// Mock dependencies and force desired code path.
|
||||||
|
sandbox.stub(uut.axios, 'get').resolves({ data: {} })
|
||||||
|
|
||||||
|
const result = await uut.detectNsfw({ mutableData: 'ipfs://bafybeibqnsmmh6bkf2wwextetki4tly65z4r4qkrrpl5xwgvzdzjley6wm' })
|
||||||
|
assert.isFalse(result)
|
||||||
|
})
|
||||||
|
it('should return false on error', async () => {
|
||||||
|
// Mock dependencies and force desired code path.
|
||||||
|
sandbox.stub(uut.axios, 'get').throws(new Error('test error'))
|
||||||
|
|
||||||
|
const result = await uut.detectNsfw({ mutableData: 'ipfs://bafybeibqnsmmh6bkf2wwextetki4tly65z4r4qkrrpl5xwgvzdzjley6wm' })
|
||||||
|
assert.isFalse(result)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('#listOffers', () => {
|
||||||
|
it('should handle error', async () => {
|
||||||
|
try {
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut.OfferModel, 'find').throws(new Error('test error'))
|
||||||
|
|
||||||
|
await uut.listOffers()
|
||||||
|
|
||||||
|
assert.fail('unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should list orders', async () => {
|
||||||
|
const queryMock = {
|
||||||
|
sort () {
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
skip () {
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
limit () { return [] }
|
||||||
|
}
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut.OfferModel, 'find').returns(queryMock)
|
||||||
|
|
||||||
|
const result = await uut.listOffers()
|
||||||
|
assert.isArray(result)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('#listNftOffers', () => {
|
||||||
|
it('should handle error', async () => {
|
||||||
|
try {
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut.OfferModel, 'find').throws(new Error('test error'))
|
||||||
|
|
||||||
|
await uut.listNftOffers()
|
||||||
|
assert.fail('unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should list orders', async () => {
|
||||||
|
const queryMock = {
|
||||||
|
sort () {
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
skip () {
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
limit () { return [] }
|
||||||
|
}
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut.OfferModel, 'find').returns(queryMock)
|
||||||
|
|
||||||
|
const result = await uut.listNftOffers(1, true)
|
||||||
|
assert.isArray(result)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('#listFungibleOffers', () => {
|
||||||
|
it('should handle error', async () => {
|
||||||
|
try {
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut.OfferModel, 'find').throws(new Error('test error'))
|
||||||
|
|
||||||
|
await uut.listFungibleOffers()
|
||||||
|
assert.fail('unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should list orders', async () => {
|
||||||
|
const queryMock = {
|
||||||
|
sort () {
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
skip () {
|
||||||
|
return this
|
||||||
|
},
|
||||||
|
limit () { return [] }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut.OfferModel, 'find').returns(queryMock)
|
||||||
|
|
||||||
|
const result = await uut.listFungibleOffers()
|
||||||
|
assert.isArray(result)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('#takeOffer', () => {
|
||||||
|
it('should handle error if input is not provided', async () => {
|
||||||
|
try {
|
||||||
|
await uut.takeOffer()
|
||||||
|
assert.fail('unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'eventId must be a string')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should handle error for wrong offer status', async () => {
|
||||||
|
try {
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut, 'findOfferByEvent').returns({ offerStatus: 'completed' })
|
||||||
|
|
||||||
|
await uut.takeOffer('eventId')
|
||||||
|
|
||||||
|
assert.fail('unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'offer status is not "posted", so offer is dead and can not be countered.')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should handle error for invalid utxo', async () => {
|
||||||
|
try {
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut, 'findOfferByEvent').returns({ offerStatus: 'posted' })
|
||||||
|
sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').returns(false)
|
||||||
|
|
||||||
|
await uut.takeOffer('eventId')
|
||||||
|
|
||||||
|
assert.fail('unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'UTXO does not exist. Aborting.')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle insufficient funds', async () => {
|
||||||
|
try {
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut, 'findOfferByEvent').returns({ offerStatus: 'posted' })
|
||||||
|
sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').returns(true)
|
||||||
|
sandbox.stub(uut, 'ensureFunds').throws(new Error('test error'))
|
||||||
|
|
||||||
|
await uut.takeOffer('eventId')
|
||||||
|
|
||||||
|
assert.fail('unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should handle error if counter offer cant be calculated', async () => {
|
||||||
|
try {
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut, 'findOfferByEvent').returns({ offerStatus: 'posted' })
|
||||||
|
sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').returns(true)
|
||||||
|
sandbox.stub(uut, 'ensureFunds').resolves(true)
|
||||||
|
|
||||||
|
await uut.takeOffer('eventId')
|
||||||
|
|
||||||
|
assert.fail('unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'Could not calculate the amount of BCH to generate counter offer')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should handle error if counter offer cant be calculated', async () => {
|
||||||
|
try {
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut, 'findOfferByEvent').returns({ offerStatus: 'posted' })
|
||||||
|
sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').returns(true)
|
||||||
|
sandbox.stub(uut, 'ensureFunds').resolves(true)
|
||||||
|
|
||||||
|
await uut.takeOffer('eventId')
|
||||||
|
|
||||||
|
assert.fail('unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'Could not calculate the amount of BCH to generate counter offer')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should take offer', async () => {
|
||||||
|
// Mock data
|
||||||
|
const offerMock = Object.assign({}, mockData.offerMockData.data)
|
||||||
|
offerMock.remove = async () => { }
|
||||||
|
offerMock.offerStatus = 'posted'
|
||||||
|
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut, 'findOfferByEvent').returns(offerMock)
|
||||||
|
sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').returns(true)
|
||||||
|
sandbox.stub(uut, 'ensureFunds').resolves(true)
|
||||||
|
sandbox.stub(uut.adapters.wallet, 'moveBch').resolves({ sats: 1 })
|
||||||
|
|
||||||
|
await uut.takeOffer('eventId')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#ensureFunds', () => {
|
||||||
|
// it('should handle insufficient funds to use p2wdb', async () => {
|
||||||
|
// try {
|
||||||
|
// // Mock dependencies
|
||||||
|
// sandbox.stub(uut.adapters.p2wdb, 'checkForSufficientFunds').resolves(false)
|
||||||
|
//
|
||||||
|
// await uut.ensureFunds(mockData.offerMockData.data)
|
||||||
|
//
|
||||||
|
// assert.fail('Unexpected code path')
|
||||||
|
// } catch (err) {
|
||||||
|
// assert.include(err.message, 'App wallet does not have funds for writing to the P2WDB')
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
|
||||||
|
it('should throw error if sats needed could no be able to calculated', async () => {
|
||||||
|
try {
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut.adapters.p2wdb, 'checkForSufficientFunds').resolves(true)
|
||||||
|
|
||||||
|
// Mock Input
|
||||||
|
const mock = Object.assign({}, mockData.offerMockData.data)
|
||||||
|
mock.rateInBaseUnit = null
|
||||||
|
|
||||||
|
await uut.ensureFunds(mock)
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'Could not calculate sats needed!')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should throw error if app wallet does not have enough bch', async () => {
|
||||||
|
try {
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut.adapters.p2wdb, 'checkForSufficientFunds').resolves(true)
|
||||||
|
sandbox.stub(uut.adapters.wallet.bchWallet, 'getBalance').resolves(0)
|
||||||
|
|
||||||
|
await uut.ensureFunds(mockData.offerMockData.data)
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'App wallet does not control enough BCH to purchase the tokens.')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should handle BUY offer', async () => {
|
||||||
|
try {
|
||||||
|
const mock = Object.assign({}, mockData.offerMockData.data)
|
||||||
|
mock.buyOrSell = 'buy'
|
||||||
|
|
||||||
|
await uut.ensureFunds(mock)
|
||||||
|
|
||||||
|
assert.fail('Unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'Buy offers are not supported yet.')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should return true', async () => {
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut.adapters.p2wdb, 'checkForSufficientFunds').resolves(true)
|
||||||
|
sandbox.stub(uut.adapters.wallet.bchWallet, 'getBalance').resolves(10 * 10 ** 6)
|
||||||
|
|
||||||
|
const result = await uut.ensureFunds(mockData.offerMockData.data)
|
||||||
|
assert.isTrue(result)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('#findOrderByEvent', () => {
|
||||||
|
it('should throw an error if hash is not provided', async () => {
|
||||||
|
try {
|
||||||
|
await uut.findOfferByEvent()
|
||||||
|
assert.fail('unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'nostrEventId must be a string')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should throw an error if order is not found!', async () => {
|
||||||
|
try {
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut.OfferModel, 'findOne').resolves(null)
|
||||||
|
|
||||||
|
await uut.findOfferByEvent('eventId')
|
||||||
|
|
||||||
|
assert.fail('unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'offer not found')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should return offer by eventId', async () => {
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut.OfferModel, 'findOne').resolves({ toObject: () => { return { hash: 'hash' } } })
|
||||||
|
|
||||||
|
const result = await uut.findOfferByEvent('eventId')
|
||||||
|
assert.isObject(result)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('#flagOffer', () => {
|
||||||
|
it('should throw an error if input is not provided', async () => {
|
||||||
|
try {
|
||||||
|
await uut.flagOffer()
|
||||||
|
assert.fail('unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, '"data" property is required')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should throw an error if offer is not found!', async () => {
|
||||||
|
try {
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut, 'findOfferByEvent').resolves(null)
|
||||||
|
|
||||||
|
const input = {
|
||||||
|
data:
|
||||||
|
{
|
||||||
|
nostrEventId: 'eventId'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await uut.flagOffer(input)
|
||||||
|
assert.fail('unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'not found in the database')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should flag offer', async () => {
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut, 'findOfferByEvent').resolves({ flags: ['a', 'b', 'c'], save: () => { } })
|
||||||
|
|
||||||
|
const input = {
|
||||||
|
data:
|
||||||
|
{
|
||||||
|
nostrEventId: 'eventId'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const result = await uut.flagOffer(input)
|
||||||
|
assert.isTrue(result)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('#loadOffers', () => {
|
||||||
|
it('should handle nostr error', async () => {
|
||||||
|
try {
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut.adapters.nostr, 'read').throws(new Error('test error'))
|
||||||
|
|
||||||
|
await uut.loadOffers()
|
||||||
|
assert.fail('unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'test error')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should skip internal function errors ', async () => {
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut.adapters.nostr, 'read').resolves([mockData.offerMockData])
|
||||||
|
|
||||||
|
await uut.loadOffers()
|
||||||
|
})
|
||||||
|
it('should review and load offers', async () => {
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut.adapters.nostr, 'read').resolves([JSON.stringify(mockData.offerMockData)])
|
||||||
|
|
||||||
|
await uut.loadOffers()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
describe('#acceptCounterOffer', () => {
|
||||||
|
it('should return if order is not found!', async () => {
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut.orderUseCase, 'findOrderByEvent').throws(new Error('test error'))
|
||||||
|
|
||||||
|
const result = await uut.acceptCounterOffer({ data: { /** .... */ } })
|
||||||
|
assert.equal(result, 'N/A')
|
||||||
|
})
|
||||||
|
it('should handle error if counter offer cant be calculated', async () => {
|
||||||
|
try {
|
||||||
|
// Mock Data
|
||||||
|
const mock = Object.assign({}, mockData.offerMockData.data)
|
||||||
|
mock.rateInBaseUnit = null
|
||||||
|
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut.orderUseCase, 'findOrderByEvent').resolves(mock)
|
||||||
|
|
||||||
|
await uut.acceptCounterOffer({ data: { /** .... */ } })
|
||||||
|
assert.fail('unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'Could not calculate the amount of BCH offered in the Counter Offer')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should handle error for wrong transaction output', async () => {
|
||||||
|
try {
|
||||||
|
// Mock dependencies
|
||||||
|
|
||||||
|
sandbox.stub(uut.orderUseCase, 'findOrderByEvent').resolves(mockData.offerMockData.data)
|
||||||
|
sandbox.stub(uut.adapters.wallet.bchWallet.bchjs.BitcoinCash, 'toSatoshi').returns(0)
|
||||||
|
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
|
||||||
|
|
||||||
|
await uut.acceptCounterOffer({ data: { /** .... */ } })
|
||||||
|
assert.fail('unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'The Counter Offer has an output of ')
|
||||||
|
assert.include(err.message, 'which does not match the required')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should handle error for wrong transaction output address', async () => {
|
||||||
|
try {
|
||||||
|
// Mock data
|
||||||
|
const mock = Object.assign({}, mockData.offerMockData.data)
|
||||||
|
mock.makerAddr = 'bitcoincash:qzy97glp47ut7tstm5g0tlrmkhk742795gkmyc7477' // Unknow Adress
|
||||||
|
mock.rateInBaseUnit = 0
|
||||||
|
mock.numTokens = 0
|
||||||
|
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut.orderUseCase, 'findOrderByEvent').resolves(mock)
|
||||||
|
sandbox.stub(uut.adapters.wallet.bchWallet.bchjs.BitcoinCash, 'toSatoshi').returns(0)
|
||||||
|
|
||||||
|
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
|
||||||
|
|
||||||
|
await uut.acceptCounterOffer({ data: { /** .... */ } })
|
||||||
|
assert.fail('unexpected code path')
|
||||||
|
} catch (err) {
|
||||||
|
assert.include(err.message, 'The Counter Offer has an output address of')
|
||||||
|
assert.include(err.message, 'which does not match the Maker address')
|
||||||
|
}
|
||||||
|
})
|
||||||
|
it('should return tx id', async () => {
|
||||||
|
// Mock data
|
||||||
|
const mock = Object.assign({}, mockData.offerMockData.data)
|
||||||
|
mock.makerAddr = mockData.deserealizeTxMock.vout[2].scriptPubKey.addresses[0] // Maker Address
|
||||||
|
mock.rateInBaseUnit = 0
|
||||||
|
mock.numTokens = 0
|
||||||
|
|
||||||
|
// Mock dependencies
|
||||||
|
sandbox.stub(uut.orderUseCase, 'findOrderByEvent').resolves(mock)
|
||||||
|
sandbox.stub(uut.adapters.wallet.bchWallet.bchjs.BitcoinCash, 'toSatoshi').returns(0)
|
||||||
|
sandbox.stub(uut.adapters.wallet, 'deseralizeTx').resolves(mockData.deserealizeTxMock)
|
||||||
|
|
||||||
|
//
|
||||||
|
const result = await uut.acceptCounterOffer({ data: { /** .... */ } })
|
||||||
|
assert.isString(result)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -9,10 +9,10 @@
|
|||||||
// Public npm libraries
|
// Public npm libraries
|
||||||
import BCHJS from '@psf/bch-js'
|
import BCHJS from '@psf/bch-js'
|
||||||
|
|
||||||
import BchTokenSweep from 'bch-token-sweep/index'
|
import BchTokenSweep from 'bch-token-sweep'
|
||||||
|
|
||||||
// Local libraries
|
// Local libraries
|
||||||
import WalletAdapter from '../../src/adapters/wallet'
|
import WalletAdapter from '../../src/adapters/wallet.js'
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
const EMTPY_ADDR_CUTOFF = 15
|
const EMTPY_ADDR_CUTOFF = 15
|
||||||
@@ -22,6 +22,7 @@ async function sweepFunds () {
|
|||||||
// Open the wallet files.
|
// Open the wallet files.
|
||||||
const wallet = new WalletAdapter()
|
const wallet = new WalletAdapter()
|
||||||
const walletInfo = await wallet.openWallet()
|
const walletInfo = await wallet.openWallet()
|
||||||
|
const bchWallet = await wallet.instanceWallet(walletInfo)
|
||||||
console.log('walletInfo: ', walletInfo)
|
console.log('walletInfo: ', walletInfo)
|
||||||
|
|
||||||
const rootAddr = walletInfo.cashAddress
|
const rootAddr = walletInfo.cashAddress
|
||||||
@@ -49,7 +50,7 @@ async function sweepFunds () {
|
|||||||
const sweeper = new BchTokenSweep(
|
const sweeper = new BchTokenSweep(
|
||||||
wifToSweep,
|
wifToSweep,
|
||||||
rootWif,
|
rootWif,
|
||||||
bchjs,
|
bchWallet,
|
||||||
550,
|
550,
|
||||||
rootAddr
|
rootAddr
|
||||||
)
|
)
|
||||||
@@ -68,7 +69,7 @@ async function sweepFunds () {
|
|||||||
// Wait between loop iterations.
|
// Wait between loop iterations.
|
||||||
await bchjs.Util.sleep(3000)
|
await bchjs.Util.sleep(3000)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.log(`error message with index ${hdIndex}: ${err.message}`)
|
console.log(`error message with index ${hdIndex}: ${err}`)
|
||||||
emptyAddrCnt++
|
emptyAddrCnt++
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user