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',
|
||||
|
||||
// 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.
|
||||
bootstrapRelays: [
|
||||
|
||||
@@ -34,7 +34,8 @@ const Offer = new mongoose.Schema({
|
||||
// SWaP Protocol Properties
|
||||
lokadId: { type: String },
|
||||
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'
|
||||
|
||||
let _this
|
||||
|
||||
class OfferRESTControllerLib {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
@@ -26,7 +24,13 @@ class OfferRESTControllerLib {
|
||||
this.OfferModel = this.adapters.localdb.Offer
|
||||
// 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
|
||||
@@ -36,7 +40,7 @@ class OfferRESTControllerLib {
|
||||
|
||||
const offerObj = ctx.request.body
|
||||
|
||||
await _this.useCases.offer.createOffer(offerObj)
|
||||
await this.useCases.offer.createOffer(offerObj)
|
||||
|
||||
ctx.body = {
|
||||
success: true
|
||||
@@ -45,7 +49,7 @@ class OfferRESTControllerLib {
|
||||
// console.log(`err.message: ${err.message}`)
|
||||
// console.log('err: ', err)
|
||||
// ctx.throw(422, err.message)
|
||||
_this.handleError(ctx, err)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,12 +59,12 @@ class OfferRESTControllerLib {
|
||||
let page = ctx.params.page
|
||||
if (!page) page = 0
|
||||
|
||||
const offers = await _this.useCases.offer.listOffers(page)
|
||||
const offers = await this.useCases.offer.listOffers(page)
|
||||
|
||||
ctx.body = offers
|
||||
} catch (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
|
||||
if (!page) page = 0
|
||||
|
||||
const offers = await _this.useCases.offer.listNftOffers(page)
|
||||
const offers = await this.useCases.offer.listNftOffers(page)
|
||||
|
||||
ctx.body = offers
|
||||
} catch (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
|
||||
if (!page) page = 0
|
||||
|
||||
const offers = await _this.useCases.offer.listFungibleOffers(page)
|
||||
const offers = await this.useCases.offer.listFungibleOffers(page)
|
||||
|
||||
ctx.body = offers
|
||||
} catch (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 {
|
||||
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) {
|
||||
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.
|
||||
this.router.post('/', _this.offerRESTController.createOffer)
|
||||
// this.router.post('/', _this.offerRESTController.createOffer) // Deprecated.
|
||||
this.router.post('/take', _this.offerRESTController.takeOffer)
|
||||
this.router.get('/list/all/:page', _this.offerRESTController.listOffers)
|
||||
this.router.get('/list/nft/:page', _this.offerRESTController.listNftOffers)
|
||||
|
||||
@@ -29,13 +29,12 @@ class TimerControllers {
|
||||
this.gcOrders = this.gcOrders.bind(this)
|
||||
this.gcOffers = this.gcOffers.bind(this)
|
||||
this.checkDupOffers = this.checkDupOffers.bind(this)
|
||||
|
||||
this.loadOffers = this.loadOffers.bind(this)
|
||||
// State
|
||||
this.gcOrdersInt = null
|
||||
this.gcOffersInt = null
|
||||
this.checkDupOffersInt = null
|
||||
|
||||
this.startTimers()
|
||||
this.loadOffersInt = null
|
||||
}
|
||||
|
||||
// Start all the time-based controllers.
|
||||
@@ -43,6 +42,8 @@ class TimerControllers {
|
||||
this.gcOrdersInt = setInterval(this.gcOrders, 60000 * 5)
|
||||
this.gcOffersInt = setInterval(this.gcOffers, 60000 * 5)
|
||||
this.checkDupOffersInt = setInterval(this.checkDupOffers, 60000 * 4.5)
|
||||
this.loadOffersInt = setInterval(this.loadOffers, 60000 * 2)
|
||||
return true
|
||||
}
|
||||
|
||||
stopTimers () {
|
||||
@@ -55,9 +56,11 @@ class TimerControllers {
|
||||
gcOrders () {
|
||||
try {
|
||||
this.useCases.order.removeStaleOrders()
|
||||
return true
|
||||
} catch (err) {
|
||||
// Do not throw an error. This is a top-level function.
|
||||
console.log('Error in timer-controllers.js/gcOrders(): ', err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -65,9 +68,11 @@ class TimerControllers {
|
||||
gcOffers () {
|
||||
try {
|
||||
this.useCases.offer.removeStaleOffers()
|
||||
return true
|
||||
} catch (err) {
|
||||
// Do not throw an error. This is a top-level function.
|
||||
console.log('Error in timer-controllers.js/gcOffers(): ', err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -75,9 +80,23 @@ class TimerControllers {
|
||||
checkDupOffers () {
|
||||
try {
|
||||
this.useCases.offer.removeDuplicateOffers()
|
||||
return true
|
||||
} catch (err) {
|
||||
// Do not throw an error. This is a top-level function.
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+108
-43
@@ -37,7 +37,7 @@ class OfferUseCases {
|
||||
this.orderUseCase = localConfig.order
|
||||
if (!this.orderUseCase) {
|
||||
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
|
||||
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
|
||||
// triggered by a P2WDB webhook.
|
||||
// This method is called by timer controller to load offers from a Nostr topic.
|
||||
async createOffer (offerObj) {
|
||||
try {
|
||||
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 {
|
||||
await this.findOfferByHash(offerObj.hash)
|
||||
await this.findOfferByTxid(offerObj.data.utxoTxid)
|
||||
|
||||
console.log('Offer already found in local database.')
|
||||
return false
|
||||
@@ -71,9 +71,9 @@ class OfferUseCases {
|
||||
// Input Validation
|
||||
// TODO: This is a hack. Find a better way to protect against the corner-
|
||||
// case of counter-offers getting routed here.
|
||||
if (offerObj.data.dataType === 'counter-offer') {
|
||||
console.log('WARN: Counter Offer innappropriately routed to createOffer()')
|
||||
}
|
||||
// if (offerObj.data.dataType === 'counter-offer') {
|
||||
// console.log('WARN: Counter Offer innappropriately routed to createOffer()')
|
||||
// }
|
||||
|
||||
// Verify that UTXO in offer is unspent. If it is spent, then ignore the
|
||||
// offer.
|
||||
@@ -90,6 +90,9 @@ class OfferUseCases {
|
||||
// A new offer gets a status of 'posted'
|
||||
offerObj.data.offerStatus = 'posted'
|
||||
|
||||
// Set timestamp
|
||||
offerObj.timestamp = new Date().getTime()
|
||||
|
||||
const offerEntity = this.offerEntity.validate(offerObj)
|
||||
console.log('offerEntity: ', offerEntity)
|
||||
|
||||
@@ -117,7 +120,7 @@ class OfferUseCases {
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in createOffer()')
|
||||
console.error('Error in createOffer()', err.message)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -140,7 +143,7 @@ class OfferUseCases {
|
||||
// Retrieve the mutable data from Filecoin/IPFS.
|
||||
// const url = `https://${cid}.ipfs.w3s.link/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
|
||||
console.log(`mutableData: ${JSON.stringify(mutableData, null, 2)}`)
|
||||
|
||||
@@ -270,12 +273,12 @@ class OfferUseCases {
|
||||
// provide functionality to take less than the total amount of tokens offered
|
||||
// (offerInfo.numTokens). Taking less than the offered amount will be added
|
||||
// in the future.
|
||||
async takeOffer (offerCid) {
|
||||
async takeOffer (eventId) {
|
||||
try {
|
||||
console.log('offerCid: ', offerCid)
|
||||
if (!eventId || typeof eventId !== 'string') throw new Error('eventId must be a string')
|
||||
|
||||
// Get the Offer information
|
||||
const offerInfo = await this.findOfferByHash(offerCid)
|
||||
const offerInfo = await this.findOfferByEvent(eventId)
|
||||
console.log(`offerInfo: ${JSON.stringify(offerInfo, null, 2)}`)
|
||||
|
||||
// Ensure the offer is in a 'posted' state and not already 'taken'
|
||||
@@ -288,6 +291,8 @@ class OfferUseCases {
|
||||
tx_hash: offerInfo.utxoTxid,
|
||||
tx_pos: offerInfo.utxoVout
|
||||
}
|
||||
|
||||
// Note : should be added to retry-queue?
|
||||
const utxoStatus = await this.adapters.wallet.bchWallet.utxoIsValid(utxo)
|
||||
console.log('utxoStatus: ', utxoStatus)
|
||||
if (!utxoStatus) {
|
||||
@@ -307,6 +312,7 @@ class OfferUseCases {
|
||||
|
||||
// Calculate amount of sats to generate a counter offer.
|
||||
let satsToMove = Math.ceil(offerInfo.numTokens * parseInt(offerInfo.rateInBaseUnit))
|
||||
console.log('satsToMove', satsToMove, offerInfo)
|
||||
if (isNaN(satsToMove)) {
|
||||
throw new Error('Could not calculate the amount of BCH to generate counter offer')
|
||||
}
|
||||
@@ -332,9 +338,9 @@ class OfferUseCases {
|
||||
// Create valid Offer object
|
||||
const takenOfferInfo = Object.assign({}, offerInfo)
|
||||
takenOfferInfo.partialTxHex = partialTxHex
|
||||
delete takenOfferInfo.p2wdbHash
|
||||
delete takenOfferInfo.nostrEventId
|
||||
delete takenOfferInfo._id
|
||||
takenOfferInfo.offerHash = offerInfo.p2wdbHash
|
||||
takenOfferInfo.offerHash = offerInfo.nostrEventId
|
||||
|
||||
// Add P2WDB specific flag for signaling that this is a new offer.
|
||||
takenOfferInfo.dataType = 'counter-offer'
|
||||
@@ -342,19 +348,19 @@ class OfferUseCases {
|
||||
// Write offer info to the P2WDB
|
||||
// TODO: This will trigger the webhook. Find some way of triggering the
|
||||
// webhook on new offers, but not on counteroffers
|
||||
const p2wdbObj = {
|
||||
const nostrData = {
|
||||
wif: this.adapters.wallet.bchWallet.walletInfo.privateKey,
|
||||
data: takenOfferInfo,
|
||||
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
|
||||
// to take the offer more than once.
|
||||
offerInfo.remove()
|
||||
|
||||
// Return the P2WDB CID
|
||||
return hash
|
||||
return resultEventId
|
||||
|
||||
// return 'fake-hash'
|
||||
} catch (err) {
|
||||
@@ -377,9 +383,10 @@ class OfferUseCases {
|
||||
await this.adapters.wallet.bchWallet.initialize()
|
||||
|
||||
// Ensure the app wallet has enough funds to write to the P2WDB.
|
||||
const wif = this.adapters.wallet.bchWallet.walletInfo.privateKey
|
||||
const canWriteToP2WDB = await this.adapters.p2wdb.checkForSufficientFunds(wif)
|
||||
if (!canWriteToP2WDB) throw new Error('App wallet does not have funds for writing to the P2WDB.')
|
||||
// Note : this validation should be deprecated for nostr functionality?
|
||||
// const wif = this.adapters.wallet.bchWallet.walletInfo.privateKey
|
||||
// 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')) {
|
||||
// Sell Offer
|
||||
@@ -417,41 +424,66 @@ class OfferUseCases {
|
||||
}
|
||||
}
|
||||
|
||||
async findOfferByHash (p2wdbHash) {
|
||||
// try {
|
||||
if (typeof p2wdbHash !== 'string' || !p2wdbHash) {
|
||||
throw new Error('p2wdbHash must be a string')
|
||||
// Retrieve an Order model from the database. Find it by its event Id.
|
||||
async findOfferByEvent (nostrEventId) {
|
||||
try {
|
||||
if (typeof nostrEventId !== 'string' || !nostrEventId) {
|
||||
throw new Error('nostrEventId must be a string')
|
||||
}
|
||||
|
||||
const offer = await this.OfferModel.findOne({ p2wdbHash })
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
async findOfferByTxid (utxoTxid) {
|
||||
try {
|
||||
// try {
|
||||
if (typeof utxoTxid !== 'string' || !utxoTxid) {
|
||||
throw new Error('utxoTxid must be a string')
|
||||
}
|
||||
|
||||
const offer = await this.OfferModel.findOne({ utxoTxid })
|
||||
|
||||
// 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
|
||||
|
||||
// const offerObject = offer.toObject()
|
||||
// return this.offerEntity.validateFromModel(offerObject)
|
||||
|
||||
// return offerObject
|
||||
// } catch (err) {
|
||||
// // console.error('Error in findOffer(): ', err)
|
||||
// throw err
|
||||
// }
|
||||
} catch (error) {
|
||||
console.error('Error in use-cases/offer/findOfferByTxid(): ', error.message)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
// to this function. It does due dilligence on the Counter Offer, then signs
|
||||
// and broadcasts the transaction to accept the Counter Offer.
|
||||
async acceptCounterOffer (p2wdbData) {
|
||||
async acceptCounterOffer (offerData) {
|
||||
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
|
||||
// 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 = {}
|
||||
try {
|
||||
orderData = await this.orderUseCase.findOrderByEvent(orderHash)
|
||||
@@ -462,12 +494,13 @@ class OfferUseCases {
|
||||
}
|
||||
|
||||
// Deserialize the partially signed transaction.
|
||||
const txHex = p2wdbData.data.partialTxHex
|
||||
const txHex = offerData.data.partialTxHex
|
||||
const txObj = await this.adapters.wallet.deseralizeTx(txHex)
|
||||
console.log(`txObj: ${JSON.stringify(txObj, null, 2)}`)
|
||||
|
||||
// Ensure the 3rd output (vout=2) contains the required amount of BCH.
|
||||
const satsToReceive = Math.ceil(orderData.numTokens * parseInt(orderData.rateInBaseUnit))
|
||||
console.log('Ceil', satsToReceive)
|
||||
if (isNaN(satsToReceive)) {
|
||||
throw new Error('Could not calculate the amount of BCH offered in the Counter Offer')
|
||||
}
|
||||
@@ -600,17 +633,21 @@ class OfferUseCases {
|
||||
}
|
||||
}
|
||||
|
||||
async flagOffer (flagData) {
|
||||
// Flag offers as NSFW.
|
||||
async flagOffer (flagData = {}) {
|
||||
try {
|
||||
if (!flagData.data) throw new Error('"data" property is required')
|
||||
|
||||
console.log(`flagData: ${JSON.stringify(flagData, null, 2)}`)
|
||||
|
||||
const p2wdbHash = flagData.data.p2wdbHash
|
||||
const eventId = flagData.data.nostrEventId
|
||||
|
||||
// Get the offer from the database.
|
||||
const offer = await this.findOfferByHash(p2wdbHash)
|
||||
const offer = await this.findOfferByEvent(eventId)
|
||||
console.log(`Flagging this offer: ${JSON.stringify(offer, null, 2)}`)
|
||||
|
||||
if (!offer) {
|
||||
throw new Error(`Offer ${p2wdbHash} not found in the database.`)
|
||||
throw new Error(`Offer ${eventId} not found in the database.`)
|
||||
}
|
||||
|
||||
// Add the raw flag data to the database model.
|
||||
@@ -626,6 +663,34 @@ class OfferUseCases {
|
||||
await offer.save()
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
console.error('Error in flagOffer(): ', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Get offers data from nostr.
|
||||
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]
|
||||
|
||||
// offer data
|
||||
const offerObj = JSON.parse(offer)
|
||||
|
||||
// Try to create new offer
|
||||
await this.createOffer(offerObj)
|
||||
} catch (error) {
|
||||
/* exit quietly */
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error in loadOffers(): ', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -55,7 +55,7 @@ class OrderLib {
|
||||
|
||||
// Ensure sufficient tokens exist to create the order.
|
||||
// await this.ensureFunds(orderEntity)
|
||||
await this.retryQueue.addToQueue(this.ensureFunds, orderEntity)
|
||||
// await this.retryQueue.addToQueue(this.ensureFunds, orderEntity)
|
||||
|
||||
// Get Ticker for token ID.
|
||||
// const tokenData = await this.adapters.wallet.bchWallet.getTxData([entryObj.tokenId])
|
||||
|
||||
@@ -56,27 +56,70 @@ describe('#Timer-Controllers', () => {
|
||||
})
|
||||
})
|
||||
|
||||
// describe('#startTimers', () => {
|
||||
// it('should start the timers', () => {
|
||||
// const result = uut.startTimers()
|
||||
//
|
||||
// // uut.stopTimers()
|
||||
//
|
||||
// assert.equal(result, true)
|
||||
// })
|
||||
// })
|
||||
describe('#startTimers', () => {
|
||||
it('should start the timers', () => {
|
||||
const result = uut.startTimers()
|
||||
|
||||
// describe('#exampleTimerFunc', () => {
|
||||
// it('should kick off the Use Case', async () => {
|
||||
// const result = await uut.exampleTimerFunc()
|
||||
//
|
||||
// assert.equal(result, true)
|
||||
// })
|
||||
//
|
||||
// it('should return false on error', async () => {
|
||||
// const result = await uut.exampleTimerFunc(true)
|
||||
//
|
||||
// assert.equal(result, false)
|
||||
// })
|
||||
// })
|
||||
// uut.stopTimers()
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#gcOrders', () => {
|
||||
it('should kick off the Use Case', async () => {
|
||||
const result = await uut.gcOrders()
|
||||
|
||||
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(),
|
||||
moveTokens: async () => {},
|
||||
reclaimTokens: async ()=>{}
|
||||
moveBch: async () => {},
|
||||
reclaimTokens: async ()=>{},
|
||||
generatePartialTx: async ()=>{},
|
||||
deseralizeTx:async ()=>{},
|
||||
completeTx:async ()=>{ return ''},
|
||||
}
|
||||
|
||||
const p2wdb = {
|
||||
|
||||
@@ -24,6 +24,7 @@ class MockBchWallet {
|
||||
this.sendTokens = async () => {
|
||||
return 'fakeTxid';
|
||||
};
|
||||
this.utxoIsValid =async ()=>{}
|
||||
this.getUtxos = async () => { };
|
||||
this.getBalance = async () => { };
|
||||
this.listTokens = async () => { };
|
||||
|
||||
@@ -41,12 +41,24 @@ class Offer {
|
||||
async createOffer() {
|
||||
return {}
|
||||
}
|
||||
async removeStaleOffers(){
|
||||
|
||||
}
|
||||
async removeDuplicateOffers(){
|
||||
|
||||
}
|
||||
async loadOffers(){
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class Order {
|
||||
async createOrder() {
|
||||
return {}
|
||||
}
|
||||
async removeStaleOrders(){
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
class UseCasesMock {
|
||||
|
||||
@@ -126,11 +126,56 @@ const fungibleTokenData01 = {
|
||||
"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 {
|
||||
nftOffer01,
|
||||
nftTokenData01,
|
||||
simpleNftOffer01,
|
||||
simpleNftTokenData01,
|
||||
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', () => {
|
||||
// it('should ignore an offer if utxo has been spent', async () => {
|
||||
// const offerObj = {
|
||||
// appId: 'swapTest555',
|
||||
// data: {
|
||||
// messageType: 1,
|
||||
// messageClass: 1,
|
||||
// tokenId:
|
||||
// '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
|
||||
// buyOrSell: 'sell',
|
||||
// rateInSats: 1000,
|
||||
// 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 handle error', async () => {
|
||||
try {
|
||||
await uut.createOffer()
|
||||
assert.fail('unexpected code path')
|
||||
} catch (error) {
|
||||
assert.include(error.message, 'Cannot read properties of undefined')
|
||||
}
|
||||
})
|
||||
it('should return false if offer already exist', async () => {
|
||||
const offerObj = mockData.offerMockData
|
||||
|
||||
// it('should create an offer and return the hash', async () => {
|
||||
// const offerObj = {
|
||||
// appId: 'swapTest555',
|
||||
// data: {
|
||||
// messageType: 1,
|
||||
// messageClass: 1,
|
||||
// tokenId:
|
||||
// '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
|
||||
// buyOrSell: 'sell',
|
||||
// rateInBaseUnit: 1000,
|
||||
// minUnitsToExchange: 10,
|
||||
// numTokens: 0.02,
|
||||
// utxoTxid:
|
||||
// '241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
|
||||
// utxoVout: 0,
|
||||
// makerAddr: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
|
||||
// ticker: 'TROUT',
|
||||
// tokenType: 1
|
||||
// },
|
||||
// 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(true)
|
||||
// sandbox.stub(uut, 'categorizeToken').resolves('fungible')
|
||||
// sandbox.stub(uut.adapters.wallet.bchWallet, 'getTokenData').resolves({})
|
||||
// sandbox.stub(uut, 'detectNsfw').resolves(false)
|
||||
//
|
||||
// const result = await uut.createOffer(offerObj)
|
||||
// // console.log('result: ', result)
|
||||
//
|
||||
// assert.equal(result, true)
|
||||
// })
|
||||
// Mock dependencies
|
||||
// sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').resolves(false)
|
||||
sandbox.stub(uut, 'findOfferByTxid').resolves({})
|
||||
|
||||
const result = await uut.createOffer(offerObj)
|
||||
assert.isFalse(result)
|
||||
})
|
||||
it('should return false for invalid utxo', async () => {
|
||||
const offerObj = mockData.offerMockData
|
||||
|
||||
// Mock dependencies
|
||||
// sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').resolves(false)
|
||||
sandbox.stub(uut, 'findOfferByTxid').throws(new Error('offer not found'))
|
||||
sandbox.stub(uut.retryQueue, 'addToQueue').resolves(null)
|
||||
|
||||
const result = await uut.createOffer(offerObj)
|
||||
assert.isFalse(result)
|
||||
})
|
||||
|
||||
it('should create offer', async () => {
|
||||
const tokenDataMock = mockData.simpleNftTokenData01
|
||||
const offerObj = mockData.offerMockData
|
||||
|
||||
// Mock dependencies
|
||||
// sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').resolves(false)
|
||||
sandbox.stub(uut, 'findOfferByTxid').throws(new Error('offer not found'))
|
||||
sandbox.stub(uut.retryQueue, 'addToQueue')
|
||||
.onCall(0).resolves({}) // Utxo Status call
|
||||
.onCall(1).resolves(tokenDataMock) // Token Data call
|
||||
.onCall(2).resolves(false) // detectNsfw call
|
||||
|
||||
const result = await uut.createOffer(offerObj)
|
||||
assert.isTrue(result)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#categorizeToken', () => {
|
||||
@@ -159,15 +147,28 @@ describe('#offer-use-case', () => {
|
||||
|
||||
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', () => {
|
||||
it('should remove duplicate entries and return true', async () => {
|
||||
// Mock dependencies and force desired code path.
|
||||
sandbox.stub(uut.OfferModel, 'find').resolves([
|
||||
{ p2wdbHash: 'a', remove: async () => {} },
|
||||
{ p2wdbHash: 'a', remove: async () => {} },
|
||||
{ p2wdbHash: 'b', remove: async () => {} }
|
||||
{ p2wdbHash: 'a', remove: async () => { } },
|
||||
{ p2wdbHash: 'a', remove: async () => { } },
|
||||
{ p2wdbHash: 'b', remove: async () => { } }
|
||||
])
|
||||
// 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 () => {
|
||||
// Mock dependencies and force desired code path.
|
||||
sandbox.stub(uut.OfferModel, 'find').resolves([
|
||||
{ p2wdbHash: 'a', remove: async () => {} },
|
||||
{ p2wdbHash: 'b', remove: async () => {} }
|
||||
{ p2wdbHash: 'a', remove: async () => { } },
|
||||
{ p2wdbHash: 'b', remove: async () => { } }
|
||||
])
|
||||
// sandbox.stub(uut.OfferModel, 'remove').resolves()
|
||||
|
||||
@@ -190,5 +191,554 @@ describe('#offer-use-case', () => {
|
||||
|
||||
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
|
||||
import BCHJS from '@psf/bch-js'
|
||||
|
||||
import BchTokenSweep from 'bch-token-sweep/index'
|
||||
import BchTokenSweep from 'bch-token-sweep'
|
||||
|
||||
// Local libraries
|
||||
import WalletAdapter from '../../src/adapters/wallet'
|
||||
import WalletAdapter from '../../src/adapters/wallet.js'
|
||||
|
||||
// Constants
|
||||
const EMTPY_ADDR_CUTOFF = 15
|
||||
@@ -22,6 +22,7 @@ async function sweepFunds () {
|
||||
// Open the wallet files.
|
||||
const wallet = new WalletAdapter()
|
||||
const walletInfo = await wallet.openWallet()
|
||||
const bchWallet = await wallet.instanceWallet(walletInfo)
|
||||
console.log('walletInfo: ', walletInfo)
|
||||
|
||||
const rootAddr = walletInfo.cashAddress
|
||||
@@ -49,7 +50,7 @@ async function sweepFunds () {
|
||||
const sweeper = new BchTokenSweep(
|
||||
wifToSweep,
|
||||
rootWif,
|
||||
bchjs,
|
||||
bchWallet,
|
||||
550,
|
||||
rootAddr
|
||||
)
|
||||
@@ -68,7 +69,7 @@ async function sweepFunds () {
|
||||
// Wait between loop iterations.
|
||||
await bchjs.Util.sleep(3000)
|
||||
} catch (err) {
|
||||
console.log(`error message with index ${hdIndex}: ${err.message}`)
|
||||
console.log(`error message with index ${hdIndex}: ${err}`)
|
||||
emptyAddrCnt++
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user