mirror of
https://github.com/Permissionless-Software-Foundation/bch-dex.git
synced 2026-09-21 16:52:00 -07:00
feat(order/offer): Refactored code to switch Order and Offer terminology
This commit is contained in:
@@ -0,0 +1,8 @@
|
||||
[
|
||||
{
|
||||
"srcDir": "",
|
||||
"destDir": "",
|
||||
"files": "**/*.js",
|
||||
"command": "npm run lint"
|
||||
}
|
||||
]
|
||||
+2
-2
@@ -94,13 +94,13 @@ class Server {
|
||||
try {
|
||||
try {
|
||||
// Delete an old webhook if it exists.
|
||||
await webhookLib.deleteWebhook(`http://localhost:${config.port}/order`)
|
||||
await webhookLib.deleteWebhook(`http://localhost:${config.port}/offer`)
|
||||
} catch (err) {
|
||||
/* exit quietly */
|
||||
// console.log('err deleting webhook: ', err)
|
||||
}
|
||||
|
||||
await webhookLib.createWebhook(`http://localhost:${config.port}/order`)
|
||||
await webhookLib.createWebhook(`http://localhost:${config.port}/offer`)
|
||||
console.log('Webhook created')
|
||||
} catch (error) {
|
||||
console.log('Webhook cant be created')
|
||||
|
||||
@@ -1,3 +1,9 @@
|
||||
/*
|
||||
Order Model. Orders are 'internal' to the system and track the HD wallet index
|
||||
that contains funds for that Order. This is in contrast to Offers, which
|
||||
are 'external' to the system and mirrored by every instance of bch-dex.
|
||||
*/
|
||||
|
||||
const mongoose = require('mongoose')
|
||||
|
||||
const Order = new mongoose.Schema({
|
||||
@@ -27,7 +33,6 @@ const Order = new mongoose.Schema({
|
||||
lokadId: { type: String },
|
||||
messageType: { type: Number },
|
||||
messageClass: { type: Number }
|
||||
|
||||
})
|
||||
|
||||
module.exports = mongoose.model('order', Order)
|
||||
|
||||
@@ -32,13 +32,15 @@ class OfferRESTControllerLib {
|
||||
// No api-doc documentation because this wont be a public endpoint
|
||||
async createOffer (ctx) {
|
||||
try {
|
||||
// console.log('body: ', ctx.request.body)
|
||||
console.log('body: ', ctx.request.body)
|
||||
|
||||
const offerObj = ctx.request.body.offer
|
||||
const offerObj = ctx.request.body
|
||||
|
||||
const hash = await _this.useCases.offer.createOffer(offerObj)
|
||||
await _this.useCases.offer.createOffer(offerObj)
|
||||
|
||||
ctx.body = { hash }
|
||||
ctx.body = {
|
||||
success: true
|
||||
}
|
||||
} catch (err) {
|
||||
// console.log(`err.message: ${err.message}`)
|
||||
// console.log('err: ', err)
|
||||
@@ -47,10 +49,42 @@ class OfferRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
// curl -X GET http://localhost:5700/offer/list
|
||||
async listOffers (ctx) {
|
||||
try {
|
||||
const offers = await _this.useCases.offer.listOffers()
|
||||
|
||||
ctx.body = offers
|
||||
} catch (err) {
|
||||
console.log('Error in listOffers REST API handler.')
|
||||
_this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Currently only supports 'sell' offers, and will only buy the 'numTokens'
|
||||
// listed in the offer.
|
||||
async takeOffer (ctx) {
|
||||
try {
|
||||
console.log('body: ', ctx.request.body)
|
||||
|
||||
const offerCid = ctx.request.body.offerCid
|
||||
|
||||
// Find the Offer.
|
||||
// const offerEntity = await _this.useCases.offer.findOffer(offerId)
|
||||
|
||||
// 'Take' the Offer.
|
||||
const hash = await _this.useCases.offer.takeOffer(offerCid)
|
||||
|
||||
ctx.body = { hash }
|
||||
} catch (err) {
|
||||
console.log('Error in takeOffer REST API handler.')
|
||||
_this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// DRY error handler
|
||||
handleError (ctx, err) {
|
||||
console.log('err', err)
|
||||
|
||||
console.log('err', err.message)
|
||||
// If an HTTP status is specified by the buisiness logic, use that.
|
||||
if (err.status) {
|
||||
if (err.message) {
|
||||
|
||||
@@ -50,6 +50,8 @@ class OfferRouter {
|
||||
|
||||
// Define the routes and attach the controller.
|
||||
this.router.post('/', _this.offerRESTController.createOffer)
|
||||
this.router.get('/list', _this.offerRESTController.listOffers)
|
||||
this.router.post('/take', _this.offerRESTController.takeOffer)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(_this.router.routes())
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
REST API Controller library for the /offer route
|
||||
REST API Controller library for the /order route
|
||||
*/
|
||||
|
||||
// const { wlogger } = require('../../../adapters/wlogger')
|
||||
@@ -32,15 +32,13 @@ class OrderRESTControllerLib {
|
||||
// No api-doc documentation because this wont be a public endpoint
|
||||
async createOrder (ctx) {
|
||||
try {
|
||||
console.log('body: ', ctx.request.body)
|
||||
// console.log('body: ', ctx.request.body)
|
||||
|
||||
const orderObj = ctx.request.body
|
||||
const orderObj = ctx.request.body.order
|
||||
|
||||
await _this.useCases.order.createOrder(orderObj)
|
||||
const hash = await _this.useCases.order.createOrder(orderObj)
|
||||
|
||||
ctx.body = {
|
||||
success: true
|
||||
}
|
||||
ctx.body = { hash }
|
||||
} catch (err) {
|
||||
// console.log(`err.message: ${err.message}`)
|
||||
// console.log('err: ', err)
|
||||
@@ -49,42 +47,10 @@ class OrderRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
// curl -X GET http://localhost:5700/order/list
|
||||
async listOrders (ctx) {
|
||||
try {
|
||||
const orders = await _this.useCases.order.listOrders()
|
||||
|
||||
ctx.body = orders
|
||||
} catch (err) {
|
||||
console.log('Error in listOrders REST API handler.')
|
||||
_this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Currently only supports 'sell' orders, and will only buy the 'numTokens'
|
||||
// listed in the order.
|
||||
async takeOrder (ctx) {
|
||||
try {
|
||||
console.log('body: ', ctx.request.body)
|
||||
|
||||
const orderCid = ctx.request.body.orderCid
|
||||
|
||||
// Find the Order.
|
||||
// const orderEntity = await _this.useCases.order.findOrder(orderId)
|
||||
|
||||
// 'Take' the Order.
|
||||
const hash = await _this.useCases.order.takeOrder(orderCid)
|
||||
|
||||
ctx.body = { hash }
|
||||
} catch (err) {
|
||||
console.log('Error in takeOrder REST API handler.')
|
||||
_this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// DRY error handler
|
||||
handleError (ctx, err) {
|
||||
console.log('err', err.message)
|
||||
console.log('err', err)
|
||||
|
||||
// If an HTTP status is specified by the buisiness logic, use that.
|
||||
if (err.status) {
|
||||
if (err.message) {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
REST API library for /offer route.
|
||||
REST API library for /order route.
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
@@ -50,8 +50,6 @@ class OrderRouter {
|
||||
|
||||
// Define the routes and attach the controller.
|
||||
this.router.post('/', _this.orderRESTController.createOrder)
|
||||
this.router.get('/list', _this.orderRESTController.listOrders)
|
||||
this.router.post('/take', _this.orderRESTController.takeOrder)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(_this.router.routes())
|
||||
|
||||
+37
-20
@@ -1,22 +1,23 @@
|
||||
/*
|
||||
Offer Entity
|
||||
An Offer Entity is nearly the same as an Order. But while an Order is generated
|
||||
by a webhook from P2WDB, the Offer Entity is created internally. It is used
|
||||
to track an Order generated by this application.
|
||||
|
||||
The Offer tracks the hdIndex address used to hold tokens or BCH for sale.
|
||||
An ffer is created when a new Signal is detected via the P2WDB webhook.
|
||||
It's destroyed when the UTXO described in the Signal has been detected as spent.
|
||||
*/
|
||||
class Offer {
|
||||
validate (data) {
|
||||
const {
|
||||
messageType,
|
||||
messageClass,
|
||||
tokenId,
|
||||
buyOrSell,
|
||||
rateInSats,
|
||||
minSatsToExchange,
|
||||
numTokens
|
||||
} = data
|
||||
|
||||
class OfferEntity {
|
||||
constructor () {
|
||||
this.orderStatus = ['posted', 'taken', 'completed']
|
||||
}
|
||||
|
||||
validate (orderData = {}) {
|
||||
// Throw an error if input object does not have a data property
|
||||
if (!orderData.data) {
|
||||
throw new Error(
|
||||
'Input to order.validate() must be an object with a data property.'
|
||||
)
|
||||
}
|
||||
|
||||
const { messageType, messageClass, tokenId, buyOrSell, rateInSats, minSatsToExchange, numTokens, utxoTxid, utxoVout, orderStatus } = orderData.data
|
||||
|
||||
// Input Validation
|
||||
if (!messageType || typeof messageType !== 'number') {
|
||||
@@ -40,19 +41,35 @@ class Offer {
|
||||
if (!numTokens || typeof numTokens !== 'number') {
|
||||
throw new Error("Property 'numTokens' must be a number.")
|
||||
}
|
||||
if (!utxoTxid || typeof utxoTxid !== 'string') {
|
||||
throw new Error("Property 'utxoTxid' must be a string.")
|
||||
}
|
||||
if (typeof utxoVout !== 'number') {
|
||||
throw new Error("Property 'utxoVout' must be an integer number.")
|
||||
}
|
||||
if (orderStatus && !this.orderStatus.includes(orderStatus)) {
|
||||
throw new Error("Property 'orderStatus' must be a valid string")
|
||||
}
|
||||
|
||||
const offerData = {
|
||||
const validatedOfferData = {
|
||||
messageType,
|
||||
messageClass,
|
||||
tokenId,
|
||||
buyOrSell,
|
||||
rateInSats,
|
||||
minSatsToExchange,
|
||||
numTokens
|
||||
numTokens,
|
||||
utxoTxid,
|
||||
utxoVout,
|
||||
timestamp: orderData.timestamp,
|
||||
localTimestamp: orderData.localTimeStamp,
|
||||
txid: orderData.txid,
|
||||
p2wdbHash: orderData.hash,
|
||||
orderStatus: orderStatus || this.orderStatus[0]
|
||||
}
|
||||
|
||||
return offerData
|
||||
return validatedOfferData
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Offer
|
||||
module.exports = OfferEntity
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
/*
|
||||
Order Entity
|
||||
An order is created when a new Signal is detected via the P2WDB webhook.
|
||||
It's destroyed when the UTXO described in the Signal has been detected as spent.
|
||||
|
||||
{
|
||||
lokadId: 'SWP', // Placeholder for now, use for backwards compatibility
|
||||
messageType: 1, // SLP Atomic Swap
|
||||
messageClass: 1,
|
||||
tokenId: '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
|
||||
buyOrSell: 'sell',
|
||||
rateInSats: 7972, // Price per token in sats
|
||||
minSatsToExchange: 8100, // Minum size of UTXO to use, e.g. 1 token + miner fees
|
||||
|
||||
// Signature from the address holding the tokens, provides 'proof of reserves'
|
||||
signature: 'H2Sq0UPh0jgs1Zt3JERHtbzfPGXJk9DgJ0FVxVa6iUqiIh6XcvEUFBbvYIuODQs3hYSCkkjcuzbvzNEiv69kFKg=',
|
||||
sigMsg: 'test',
|
||||
address: 'bitcoincash:qphjncqpnv444jq8acqk4dkm3296c50xhqggeatvn8',
|
||||
|
||||
// UTXO for sale
|
||||
utxoTxid: 'b9457808be70c39a9cc6c5857cbef856b35fdc91a59debfe06acfc45b11955e3',
|
||||
utxoVout: 2
|
||||
}
|
||||
*/
|
||||
+20
-36
@@ -1,22 +1,22 @@
|
||||
/*
|
||||
Order Entity
|
||||
An order is created when a new Signal is detected via the P2WDB webhook.
|
||||
It's destroyed when the UTXO described in the Signal has been detected as spent.
|
||||
An Order Entity is nearly the same as an Offer. But while an Offer is generated
|
||||
by a webhook from P2WDB, the Order Entity is created internally. It is used
|
||||
to track an Order generated by this application.
|
||||
|
||||
The Order tracks the hdIndex address used to hold tokens or BCH for sale.
|
||||
*/
|
||||
class OrderEntity {
|
||||
constructor () {
|
||||
this.orderStatus = ['posted', 'taken', 'completed']
|
||||
}
|
||||
|
||||
validate (orderData = {}) {
|
||||
// Throw an error if input object does not have a data property
|
||||
if (!orderData.data) {
|
||||
throw new Error(
|
||||
'Input to order.validate() must be an object with a data property.'
|
||||
)
|
||||
}
|
||||
|
||||
const { messageType, messageClass, tokenId, buyOrSell, rateInSats, minSatsToExchange, numTokens, utxoTxid, utxoVout, orderStatus } = orderData.data
|
||||
class Order {
|
||||
validate (data) {
|
||||
const {
|
||||
messageType,
|
||||
messageClass,
|
||||
tokenId,
|
||||
buyOrSell,
|
||||
rateInSats,
|
||||
minSatsToExchange,
|
||||
numTokens
|
||||
} = data
|
||||
|
||||
// Input Validation
|
||||
if (!messageType || typeof messageType !== 'number') {
|
||||
@@ -40,35 +40,19 @@ class OrderEntity {
|
||||
if (!numTokens || typeof numTokens !== 'number') {
|
||||
throw new Error("Property 'numTokens' must be a number.")
|
||||
}
|
||||
if (!utxoTxid || typeof utxoTxid !== 'string') {
|
||||
throw new Error("Property 'utxoTxid' must be a string.")
|
||||
}
|
||||
if (typeof utxoVout !== 'number') {
|
||||
throw new Error("Property 'utxoVout' must be an integer number.")
|
||||
}
|
||||
if (orderStatus && !this.orderStatus.includes(orderStatus)) {
|
||||
throw new Error("Property 'orderStatus' must be a valid string")
|
||||
}
|
||||
|
||||
const validatedOrderData = {
|
||||
const offerData = {
|
||||
messageType,
|
||||
messageClass,
|
||||
tokenId,
|
||||
buyOrSell,
|
||||
rateInSats,
|
||||
minSatsToExchange,
|
||||
numTokens,
|
||||
utxoTxid,
|
||||
utxoVout,
|
||||
timestamp: orderData.timestamp,
|
||||
localTimestamp: orderData.localTimeStamp,
|
||||
txid: orderData.txid,
|
||||
p2wdbHash: orderData.hash,
|
||||
orderStatus: orderStatus || this.orderStatus[0]
|
||||
numTokens
|
||||
}
|
||||
|
||||
return validatedOrderData
|
||||
return offerData
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OrderEntity
|
||||
module.exports = Order
|
||||
|
||||
@@ -1,46 +1,46 @@
|
||||
/*
|
||||
Use Case library for Orders.
|
||||
Orders are created by a webhook trigger from the P2WDB. Orders are a result of
|
||||
Use Case library for Offers.
|
||||
Offers are created by a webhook trigger from the P2WDB. Offers are a result of
|
||||
new data in P2WDB. They differ from Offers, which are generated by a local
|
||||
user.
|
||||
An Order is created to match a local Offer, but it's created indirectly, as
|
||||
a response to the webhook from the P2WDB. In this way, Orders generated from
|
||||
local Offers are no different than Orders generated by other peers.
|
||||
An Offer is created to match a local Offer, but it's created indirectly, as
|
||||
a response to the webhook from the P2WDB. In this way, Offers generated from
|
||||
local Offers are no different than Offers generated by other peers.
|
||||
*/
|
||||
|
||||
// Local libraries
|
||||
const OrderEntity = require('../../entities/order')
|
||||
const OfferEntity = require('../../entities/offer')
|
||||
const config = require('../../../config')
|
||||
|
||||
class OrderUseCases {
|
||||
class OfferUseCases {
|
||||
constructor (localConfig = {}) {
|
||||
// console.log('User localConfig: ', localConfig)
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of adapters must be passed in when instantiating Order Use Cases library.'
|
||||
'Instance of adapters must be passed in when instantiating Offer Use Cases library.'
|
||||
)
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.config = config
|
||||
|
||||
this.orderEntity = new OrderEntity()
|
||||
this.OrderModel = this.adapters.localdb.Order
|
||||
this.offerEntity = new OfferEntity()
|
||||
this.OfferModel = this.adapters.localdb.Offer
|
||||
}
|
||||
|
||||
// This method is called by the POST /order REST API controller, which is
|
||||
// This method is called by the POST /offer REST API controller, which is
|
||||
// triggered by a P2WDB webhook.
|
||||
async createOrder (orderObj) {
|
||||
async createOffer (offerObj) {
|
||||
try {
|
||||
console.log('Use Case createOrder(orderObj): ', orderObj)
|
||||
console.log('Use Case createOffer(offerObj): ', offerObj)
|
||||
|
||||
// console.log('this.adapters.bchjs: ', this.adapters.bchjs)
|
||||
|
||||
// Verify that UTXO in order is unspent. If it is spent, then ignore the
|
||||
// order.
|
||||
const txid = orderObj.data.utxoTxid
|
||||
const vout = orderObj.data.utxoVout
|
||||
// Verify that UTXO in offer is unspent. If it is spent, then ignore the
|
||||
// offer.
|
||||
const txid = offerObj.data.utxoTxid
|
||||
const vout = offerObj.data.utxoVout
|
||||
const utxoStatus = await this.adapters.bchjs.Blockchain.getTxOut(
|
||||
txid,
|
||||
vout
|
||||
@@ -48,51 +48,51 @@ class OrderUseCases {
|
||||
console.log('utxoStatus: ', utxoStatus)
|
||||
if (utxoStatus === null) return false
|
||||
|
||||
// A new order gets a status of 'posted'
|
||||
orderObj.data.orderStatus = 'posted'
|
||||
// A new offer gets a status of 'posted'
|
||||
offerObj.data.offerStatus = 'posted'
|
||||
|
||||
const orderEntity = this.orderEntity.validate(orderObj)
|
||||
console.log('orderEntity: ', orderEntity)
|
||||
const offerEntity = this.offerEntity.validate(offerObj)
|
||||
console.log('offerEntity: ', offerEntity)
|
||||
|
||||
// Add order to the local database.
|
||||
const orderModel = new this.OrderModel(orderEntity)
|
||||
await orderModel.save()
|
||||
// Add offer to the local database.
|
||||
const offerModel = new this.OfferModel(offerEntity)
|
||||
await offerModel.save()
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in createOrder()')
|
||||
console.error('Error in createOffer()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async listOrders () {
|
||||
async listOffers () {
|
||||
try {
|
||||
return this.OrderModel.find({})
|
||||
return this.OfferModel.find({})
|
||||
} catch (error) {
|
||||
console.error('Error in use-cases/order/listOrders()')
|
||||
console.error('Error in use-cases/offer/listOffers()')
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Generate phase 2 of 3 - take the other side of an Order.
|
||||
// Generate phase 2 of 3 - take the other side of an Offer.
|
||||
// Based on this example:
|
||||
// https://github.com/Permissionless-Software-Foundation/bch-js-examples/blob/master/bch/applications/collaborate/sell-slp/e2e-exchange/step2-purchase-tx.js
|
||||
async takeOrder (orderCid) {
|
||||
async takeOffer (offerCid) {
|
||||
try {
|
||||
console.log('orderCid: ', orderCid)
|
||||
console.log('offerCid: ', offerCid)
|
||||
|
||||
// Get the Order information
|
||||
const orderInfo = await this.findOrderByHash(orderCid)
|
||||
console.log(`orderInfo: ${JSON.stringify(orderInfo, null, 2)}`)
|
||||
// Get the Offer information
|
||||
const offerInfo = await this.findOfferByHash(offerCid)
|
||||
console.log(`offerInfo: ${JSON.stringify(offerInfo, null, 2)}`)
|
||||
|
||||
// Ensure the order is in a 'posted' state and not already 'taken'
|
||||
if (orderInfo.orderStatus && orderInfo.orderStatus !== 'posted') {
|
||||
throw new Error('order already taken')
|
||||
// Ensure the offer is in a 'posted' state and not already 'taken'
|
||||
if (offerInfo.offerStatus && offerInfo.offerStatus !== 'posted') {
|
||||
throw new Error('offer already taken')
|
||||
}
|
||||
|
||||
// Verify that UTXO for sale is unspent. Abort if it's been spent.
|
||||
const txid = orderInfo.utxoTxid
|
||||
const vout = orderInfo.utxoVout
|
||||
const txid = offerInfo.utxoTxid
|
||||
const vout = offerInfo.utxoVout
|
||||
const utxoStatus = await this.adapters.bchjs.Blockchain.getTxOut(
|
||||
txid,
|
||||
vout
|
||||
@@ -104,32 +104,32 @@ class OrderUseCases {
|
||||
}
|
||||
|
||||
// Ensure the app has enough funds to complete the trade.
|
||||
await this.ensureFunds(orderInfo)
|
||||
await this.ensureFunds(offerInfo)
|
||||
|
||||
// Get UTXOs.
|
||||
const utxos = this.adapters.wallet.bchWallet.utxos.utxoStore
|
||||
console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
|
||||
|
||||
// TODO: Move funds to create a segrated UTXO for taking the order
|
||||
// TODO: Move funds to create a segrated UTXO for taking the offer
|
||||
|
||||
// Create a partially signed transaction.
|
||||
// https://github.com/Permissionless-Software-Foundation/bch-js-examples/blob/master/bch/applications/collaborate/sell-slp/e2e-exchange/step2-purchase-tx.js#L59
|
||||
const partialTxHex = await this.adapters.wallet.generatePartialTx(orderInfo)
|
||||
const partialTxHex = await this.adapters.wallet.generatePartialTx(offerInfo)
|
||||
// return partialTxHex
|
||||
|
||||
// Create valid Order object
|
||||
const takenOrderInfo = Object.assign({}, orderInfo)
|
||||
takenOrderInfo.patialTxHex = partialTxHex
|
||||
delete takenOrderInfo.p2wdbHash
|
||||
delete takenOrderInfo._id
|
||||
takenOrderInfo.offerHash = orderInfo.p2wdbHash
|
||||
// Create valid Offer object
|
||||
const takenOfferInfo = Object.assign({}, offerInfo)
|
||||
takenOfferInfo.patialTxHex = partialTxHex
|
||||
delete takenOfferInfo.p2wdbHash
|
||||
delete takenOfferInfo._id
|
||||
takenOfferInfo.offerHash = offerInfo.p2wdbHash
|
||||
|
||||
// Write order info to the P2WDB
|
||||
// Write offer info to the P2WDB
|
||||
// TODO: This will trigger the webhook. Find some way of triggering the
|
||||
// webhook on new orders, but not on counteroffers
|
||||
// webhook on new offers, but not on counteroffers
|
||||
const p2wdbObj = {
|
||||
wif: this.adapters.wallet.bchWallet.walletInfo.privateKey,
|
||||
data: takenOrderInfo,
|
||||
data: takenOfferInfo,
|
||||
appId: this.config.p2wdbAppId
|
||||
}
|
||||
const hash = await this.adapters.p2wdb.write(p2wdbObj)
|
||||
@@ -137,14 +137,14 @@ class OrderUseCases {
|
||||
// Return the P2WDB CID
|
||||
return hash
|
||||
} catch (err) {
|
||||
console.error('Error in use-cases/order/takeOrder(): ', err)
|
||||
console.error('Error in use-cases/offer/takeOffer(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that the wallet has enough BCH and tokens to complete the requested
|
||||
// trade. Will return true if it does. Will throw an error if it doesn't.
|
||||
async ensureFunds (orderEntity) {
|
||||
async ensureFunds (offerEntity) {
|
||||
try {
|
||||
// console.log('this.adapters.wallet: ', this.adapters.wallet.bchWallet)
|
||||
// console.log(`walletInfo: ${JSON.stringify(this.adapters.wallet.bchWallet.walletInfo, null, 2)}`)
|
||||
@@ -156,11 +156,11 @@ class OrderUseCases {
|
||||
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 (orderEntity.buyOrSell.includes('sell')) {
|
||||
if (offerEntity.buyOrSell.includes('sell')) {
|
||||
// Sell Offer
|
||||
|
||||
// Ensure the app wallet controlls enough BCH to pay for the tokens.
|
||||
const satsNeeded = orderEntity.numTokens * parseInt(orderEntity.rateInSats)
|
||||
const satsNeeded = offerEntity.numTokens * parseInt(offerEntity.rateInSats)
|
||||
const balance = await this.adapters.wallet.bchWallet.getBalance()
|
||||
console.log(`wallet balance: ${balance}, sats needed: ${satsNeeded}`)
|
||||
const SATS_MARGIN = 5000
|
||||
@@ -169,7 +169,7 @@ class OrderUseCases {
|
||||
//
|
||||
} else {
|
||||
// Buy Offer
|
||||
throw new Error('Buy orders are not supported yet.')
|
||||
throw new Error('Buy offers are not supported yet.')
|
||||
}
|
||||
|
||||
return true
|
||||
@@ -179,27 +179,27 @@ class OrderUseCases {
|
||||
}
|
||||
}
|
||||
|
||||
async findOrderByHash (p2wdbHash) {
|
||||
async findOfferByHash (p2wdbHash) {
|
||||
try {
|
||||
if (typeof p2wdbHash !== 'string' || !p2wdbHash) {
|
||||
throw new Error('p2wdbHash must be a string')
|
||||
}
|
||||
|
||||
const order = await this.OrderModel.findOne({ p2wdbHash })
|
||||
const offer = await this.OfferModel.findOne({ p2wdbHash })
|
||||
|
||||
if (!order) {
|
||||
throw new Error('order not found')
|
||||
if (!offer) {
|
||||
throw new Error('offer not found')
|
||||
}
|
||||
|
||||
const orderObject = order.toObject()
|
||||
// return this.orderEntity.validateFromModel(orderObject)
|
||||
const offerObject = offer.toObject()
|
||||
// return this.offerEntity.validateFromModel(offerObject)
|
||||
|
||||
return orderObject
|
||||
return offerObject
|
||||
} catch (err) {
|
||||
console.error('Error in findOrder(): ', err)
|
||||
console.error('Error in findOffer(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OrderUseCases
|
||||
module.exports = OfferUseCases
|
||||
@@ -1,89 +1,89 @@
|
||||
/*
|
||||
Offer use-case library.
|
||||
Order use-case library.
|
||||
*/
|
||||
|
||||
// Local libraries
|
||||
const { wlogger } = require('../adapters/wlogger')
|
||||
const OfferEntity = require('../entities/offer')
|
||||
const OrderEntity = require('../entities/order')
|
||||
const config = require('../../config')
|
||||
|
||||
class OfferLib {
|
||||
class OrderLib {
|
||||
constructor (localConfig = {}) {
|
||||
// console.log('User localConfig: ', localConfig)
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of adapters must be passed in when instantiating Offer Use Cases library.'
|
||||
'Instance of adapters must be passed in when instantiating Order Use Cases library.'
|
||||
)
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.offerEntity = new OfferEntity()
|
||||
this.OfferModel = this.adapters.localdb.Offer
|
||||
this.orderEntity = new OrderEntity()
|
||||
this.OrderModel = this.adapters.localdb.Order
|
||||
this.bch = this.adapters.bch
|
||||
this.config = config
|
||||
}
|
||||
|
||||
// Create a new offer model and add it to the Mongo database.
|
||||
async createOffer (entryObj) {
|
||||
// Create a new order model and add it to the Mongo database.
|
||||
async createOrder (entryObj) {
|
||||
try {
|
||||
// console.log('createOffer(entryObj): ', entryObj)
|
||||
// console.log('createOrder(entryObj): ', entryObj)
|
||||
|
||||
// Input Validation
|
||||
const offerEntity = this.offerEntity.validate(entryObj)
|
||||
console.log('offerEntity: ', offerEntity)
|
||||
const orderEntity = this.orderEntity.validate(entryObj)
|
||||
console.log('orderEntity: ', orderEntity)
|
||||
|
||||
// Ensure sufficient tokens exist to create the offer.
|
||||
await this.ensureFunds(offerEntity)
|
||||
// Ensure sufficient tokens exist to create the order.
|
||||
await this.ensureFunds(orderEntity)
|
||||
|
||||
// Move the tokens to holding address.
|
||||
const utxoInfo = await this.moveTokens(offerEntity)
|
||||
const utxoInfo = await this.moveTokens(orderEntity)
|
||||
console.log('utxoInfo: ', utxoInfo)
|
||||
|
||||
// Update the UTXO store for the wallet.
|
||||
await this.adapters.wallet.bchWallet.bchjs.Util.sleep(3000)
|
||||
await this.adapters.wallet.bchWallet.getUtxos()
|
||||
|
||||
// Update the offer with the new UTXO information.
|
||||
offerEntity.utxoTxid = utxoInfo.txid
|
||||
offerEntity.utxoVout = utxoInfo.vout
|
||||
offerEntity.hdIndex = utxoInfo.hdIndex
|
||||
// Update the order with the new UTXO information.
|
||||
orderEntity.utxoTxid = utxoInfo.txid
|
||||
orderEntity.utxoVout = utxoInfo.vout
|
||||
orderEntity.hdIndex = utxoInfo.hdIndex
|
||||
|
||||
// Add offer to P2WDB.
|
||||
// Add order to P2WDB.
|
||||
const p2wdbObj = {
|
||||
wif: this.adapters.wallet.bchWallet.walletInfo.privateKey,
|
||||
data: offerEntity,
|
||||
data: orderEntity,
|
||||
appId: this.config.p2wdbAppId
|
||||
}
|
||||
const hash = await this.adapters.p2wdb.write(p2wdbObj)
|
||||
// console.log('hash: ', hash)
|
||||
|
||||
// Create a MongoDB model to hold the Offer
|
||||
offerEntity.p2wdbHash = hash
|
||||
console.log(`creating new offer model: ${JSON.stringify(offerEntity, null, 2)}`)
|
||||
const offer = new this.OfferModel(offerEntity)
|
||||
await offer.save()
|
||||
// Create a MongoDB model to hold the Order
|
||||
orderEntity.p2wdbHash = hash
|
||||
console.log(`creating new order model: ${JSON.stringify(orderEntity, null, 2)}`)
|
||||
const order = new this.OrderModel(orderEntity)
|
||||
await order.save()
|
||||
|
||||
return hash
|
||||
} catch (err) {
|
||||
// console.log("Error in use-cases/entry.js/createEntry()", err.message)
|
||||
wlogger.error('Error in use-cases/entry.js/createOffer())')
|
||||
wlogger.error('Error in use-cases/entry.js/createOrder())')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Move the tokens indicated in the offer to a temporary holding address.
|
||||
// Move the tokens indicated in the order to a temporary holding address.
|
||||
// This will generate the UTXO used in the Signal message. This function
|
||||
// moves the funds and returns the UTXO information.
|
||||
async moveTokens (offerEntity) {
|
||||
async moveTokens (orderEntity) {
|
||||
try {
|
||||
const keyPair = await this.adapters.wallet.getKeyPair()
|
||||
console.log('keyPair: ', keyPair)
|
||||
|
||||
const receiver = {
|
||||
address: keyPair.cashAddress,
|
||||
tokenId: offerEntity.tokenId,
|
||||
qty: offerEntity.numTokens
|
||||
tokenId: orderEntity.tokenId,
|
||||
qty: orderEntity.numTokens
|
||||
}
|
||||
|
||||
const txid = await this.adapters.wallet.bchWallet.sendTokens(receiver, 3)
|
||||
@@ -103,7 +103,7 @@ class OfferLib {
|
||||
|
||||
// Ensure that the wallet has enough BCH and tokens to complete the requested
|
||||
// trade.
|
||||
async ensureFunds (offerEntity) {
|
||||
async ensureFunds (orderEntity) {
|
||||
try {
|
||||
// console.log('this.adapters.wallet: ', this.adapters.wallet.bchWallet)
|
||||
// console.log(`walletInfo: ${JSON.stringify(this.adapters.wallet.bchWallet.walletInfo, null, 2)}`)
|
||||
@@ -117,32 +117,32 @@ class OfferLib {
|
||||
const utxos = this.adapters.wallet.bchWallet.utxos.utxoStore
|
||||
console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
|
||||
|
||||
if (offerEntity.buyOrSell.includes('sell')) {
|
||||
// Sell Offer
|
||||
if (orderEntity.buyOrSell.includes('sell')) {
|
||||
// Sell Order
|
||||
|
||||
// Get token UTXOs that match the token in the offer.
|
||||
// Get token UTXOs that match the token in the order.
|
||||
const tokenUtxos = utxos.slpUtxos.type1.tokens.filter(
|
||||
x => x.tokenId === offerEntity.tokenId
|
||||
x => x.tokenId === orderEntity.tokenId
|
||||
)
|
||||
// console.log('tokenUtxos: ', tokenUtxos)
|
||||
|
||||
// Get the total amount of tokens in the wallet that match the token
|
||||
// in the offer.
|
||||
// in the order.
|
||||
let totalTokenBalance = 0
|
||||
tokenUtxos.map(x => (totalTokenBalance += parseFloat(x.qtyStr)))
|
||||
console.log('totalTokenBalance: ', totalTokenBalance)
|
||||
|
||||
// If there are fewer tokens in the wallet than what's in the offer,
|
||||
// If there are fewer tokens in the wallet than what's in the order,
|
||||
// throw an error.
|
||||
if (totalTokenBalance <= offerEntity.numTokens || isNaN(totalTokenBalance)) {
|
||||
if (totalTokenBalance <= orderEntity.numTokens || isNaN(totalTokenBalance)) {
|
||||
throw new Error(
|
||||
'App wallet does not have enough tokens to satisfy the SELL offer.'
|
||||
'App wallet does not have enough tokens to satisfy the SELL order.'
|
||||
)
|
||||
}
|
||||
|
||||
//
|
||||
} else {
|
||||
// Buy Offer
|
||||
// Buy Order
|
||||
throw new Error('Buy orders are not supported yet.')
|
||||
}
|
||||
|
||||
@@ -154,4 +154,4 @@ class OfferLib {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OfferLib
|
||||
module.exports = OrderLib
|
||||
@@ -1,5 +1,6 @@
|
||||
/*
|
||||
A manual e2e test for creating an swap offer.
|
||||
A manual e2e test for creating an Order, which then generates an Offer through
|
||||
the P2WDB webhook.
|
||||
|
||||
Ensure the REST API is up an running before running this test.
|
||||
*/
|
||||
@@ -10,7 +11,7 @@ const LOCALHOST = 'http://localhost:5700'
|
||||
|
||||
async function start () {
|
||||
try {
|
||||
const mockOffer = {
|
||||
const mockOrder = {
|
||||
lokadId: 'SWP',
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
@@ -24,8 +25,8 @@ async function start () {
|
||||
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: `${LOCALHOST}/offer`,
|
||||
data: { offer: mockOffer }
|
||||
url: `${LOCALHOST}/order`,
|
||||
data: { order: mockOrder }
|
||||
}
|
||||
|
||||
const result = await axios(options)
|
||||
|
||||
@@ -64,36 +64,53 @@ describe('#Offer-REST-Router', () => {
|
||||
describe('#createOffer', () => {
|
||||
it('should create a new offer', async () => {
|
||||
ctx.request.body = {
|
||||
offer: {}
|
||||
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.useCases.offer, 'createOffer').resolves('testHash')
|
||||
// sandbox.stub(uut.useCases.offer, 'createOffer').resolves()
|
||||
|
||||
await uut.createOffer(ctx)
|
||||
|
||||
assert.equal(ctx.body.hash, 'testHash')
|
||||
// assert.equal(ctx.body.hash, 'testHash')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
ctx.request.body = {
|
||||
offer: {}
|
||||
}
|
||||
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.useCases.offer, 'createOffer')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
await uut.createOffer(ctx)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
// it('should catch and throw an error', async () => {
|
||||
// try {
|
||||
// ctx.request.body = {
|
||||
// offer: {}
|
||||
// }
|
||||
//
|
||||
// // Force an error
|
||||
// sandbox
|
||||
// .stub(uut.useCases.offer, 'createOffer')
|
||||
// .rejects(new Error('test error'))
|
||||
//
|
||||
// await uut.createOffer(ctx)
|
||||
//
|
||||
// assert.fail('Unexpected code path')
|
||||
// } catch (err) {
|
||||
// // console.log('err: ', err)
|
||||
// assert.include(err.message, 'test error')
|
||||
// }
|
||||
// })
|
||||
})
|
||||
|
||||
describe('#handleError', () => {
|
||||
|
||||
@@ -64,53 +64,36 @@ describe('#Order-REST-Router', () => {
|
||||
describe('#createOrder', () => {
|
||||
it('should create a new order', async () => {
|
||||
ctx.request.body = {
|
||||
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'
|
||||
order: {}
|
||||
}
|
||||
|
||||
// Mock dependencies
|
||||
// sandbox.stub(uut.useCases.order, 'createOrder').resolves()
|
||||
sandbox.stub(uut.useCases.order, 'createOrder').resolves('testHash')
|
||||
|
||||
await uut.createOrder(ctx)
|
||||
|
||||
// assert.equal(ctx.body.hash, 'testHash')
|
||||
assert.equal(ctx.body.hash, 'testHash')
|
||||
})
|
||||
|
||||
// it('should catch and throw an error', async () => {
|
||||
// try {
|
||||
// ctx.request.body = {
|
||||
// order: {}
|
||||
// }
|
||||
//
|
||||
// // Force an error
|
||||
// sandbox
|
||||
// .stub(uut.useCases.order, 'createOrder')
|
||||
// .rejects(new Error('test error'))
|
||||
//
|
||||
// await uut.createOrder(ctx)
|
||||
//
|
||||
// assert.fail('Unexpected code path')
|
||||
// } catch (err) {
|
||||
// // console.log('err: ', err)
|
||||
// assert.include(err.message, 'test error')
|
||||
// }
|
||||
// })
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
ctx.request.body = {
|
||||
order: {}
|
||||
}
|
||||
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.useCases.order, 'createOrder')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
await uut.createOrder(ctx)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#handleError', () => {
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Unit tests for the User entity library.
|
||||
Unit tests for the Offer entity library.
|
||||
*/
|
||||
|
||||
const assert = require('chai').assert
|
||||
@@ -27,14 +27,17 @@ describe('#Offer-Entity', () => {
|
||||
uut.validate()
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, 'Cannot destructure property')
|
||||
assert.include(
|
||||
err.message,
|
||||
'Input to order.validate() must be an object with a data property.'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if messageType is not included', () => {
|
||||
try {
|
||||
const data = {}
|
||||
uut.validate(data)
|
||||
const orderData = { data: {} }
|
||||
uut.validate(orderData)
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
@@ -46,8 +49,8 @@ describe('#Offer-Entity', () => {
|
||||
|
||||
it('should throw an error if messageClass is not included', () => {
|
||||
try {
|
||||
const data = { messageType: 1 }
|
||||
uut.validate(data)
|
||||
const orderData = { data: { messageType: 1 } }
|
||||
uut.validate(orderData)
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
@@ -59,8 +62,8 @@ describe('#Offer-Entity', () => {
|
||||
|
||||
it('should throw an error if tokenId is not included', () => {
|
||||
try {
|
||||
const data = { messageType: 1, messageClass: 1 }
|
||||
uut.validate(data)
|
||||
const orderData = { data: { messageType: 1, messageClass: 1 } }
|
||||
uut.validate(orderData)
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, "Property 'tokenId' must be a string.")
|
||||
@@ -69,8 +72,11 @@ describe('#Offer-Entity', () => {
|
||||
|
||||
it('should throw an error if buyOrSell is not included', () => {
|
||||
try {
|
||||
const data = { messageType: 1, messageClass: 1, tokenId: 'fakeId' }
|
||||
uut.validate(data)
|
||||
const orderData = {
|
||||
data: { messageType: 1, messageClass: 1, tokenId: 'fakeId' }
|
||||
}
|
||||
|
||||
uut.validate(orderData)
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, "Property 'buyOrSell' must be a string.")
|
||||
@@ -79,13 +85,16 @@ describe('#Offer-Entity', () => {
|
||||
|
||||
it('should throw an error if rateInSats is not included', () => {
|
||||
try {
|
||||
const data = {
|
||||
const orderData = {
|
||||
data: {
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'fakeId',
|
||||
buyOrSell: 'buy'
|
||||
}
|
||||
uut.validate(data)
|
||||
}
|
||||
|
||||
uut.validate(orderData)
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
@@ -97,14 +106,17 @@ describe('#Offer-Entity', () => {
|
||||
|
||||
it('should throw an error if minSatsToExchange is not included', () => {
|
||||
try {
|
||||
const data = {
|
||||
const orderData = {
|
||||
data: {
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'fakeId',
|
||||
buyOrSell: 'buy',
|
||||
rateInSats: 1000
|
||||
}
|
||||
uut.validate(data)
|
||||
}
|
||||
|
||||
uut.validate(orderData)
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
@@ -116,7 +128,8 @@ describe('#Offer-Entity', () => {
|
||||
|
||||
it('should throw an error if numTokens is not included', () => {
|
||||
try {
|
||||
const data = {
|
||||
const orderData = {
|
||||
data: {
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'fakeId',
|
||||
@@ -124,11 +137,96 @@ describe('#Offer-Entity', () => {
|
||||
rateInSats: 1000,
|
||||
minSatsToExchange: 350
|
||||
}
|
||||
uut.validate(data)
|
||||
}
|
||||
uut.validate(orderData)
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, "Property 'numTokens' must be a number.")
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if utxoTxid is not included', () => {
|
||||
try {
|
||||
const orderData = {
|
||||
data: {
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'fakeId',
|
||||
buyOrSell: 'buy',
|
||||
rateInSats: 1000,
|
||||
minSatsToExchange: 350,
|
||||
numTokens: 1
|
||||
}
|
||||
}
|
||||
uut.validate(orderData)
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, "Property 'utxoTxid' must be a string.")
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if utxoVout is not included', () => {
|
||||
try {
|
||||
const orderData = {
|
||||
data: {
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'fakeId',
|
||||
buyOrSell: 'buy',
|
||||
rateInSats: 1000,
|
||||
minSatsToExchange: 350,
|
||||
numTokens: 1,
|
||||
utxoTxid: 'fakeTxid'
|
||||
}
|
||||
}
|
||||
uut.validate(orderData)
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
err.message,
|
||||
"Property 'utxoVout' must be an integer number."
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should validate a new order', () => {
|
||||
const orderObj = {
|
||||
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'
|
||||
}
|
||||
|
||||
const result = uut.validate(orderObj)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'messageType')
|
||||
assert.property(result, 'messageClass')
|
||||
assert.property(result, 'tokenId')
|
||||
assert.property(result, 'buyOrSell')
|
||||
assert.property(result, 'rateInSats')
|
||||
assert.property(result, 'minSatsToExchange')
|
||||
assert.property(result, 'numTokens')
|
||||
assert.property(result, 'utxoTxid')
|
||||
assert.property(result, 'utxoVout')
|
||||
assert.property(result, 'timestamp')
|
||||
assert.property(result, 'localTimestamp')
|
||||
assert.property(result, 'txid')
|
||||
assert.property(result, 'p2wdbHash')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Unit tests for the Order entity library.
|
||||
Unit tests for the User entity library.
|
||||
*/
|
||||
|
||||
const assert = require('chai').assert
|
||||
@@ -27,17 +27,14 @@ describe('#Order-Entity', () => {
|
||||
uut.validate()
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
err.message,
|
||||
'Input to order.validate() must be an object with a data property.'
|
||||
)
|
||||
assert.include(err.message, 'Cannot destructure property')
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if messageType is not included', () => {
|
||||
try {
|
||||
const orderData = { data: {} }
|
||||
uut.validate(orderData)
|
||||
const data = {}
|
||||
uut.validate(data)
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
@@ -49,8 +46,8 @@ describe('#Order-Entity', () => {
|
||||
|
||||
it('should throw an error if messageClass is not included', () => {
|
||||
try {
|
||||
const orderData = { data: { messageType: 1 } }
|
||||
uut.validate(orderData)
|
||||
const data = { messageType: 1 }
|
||||
uut.validate(data)
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
@@ -62,8 +59,8 @@ describe('#Order-Entity', () => {
|
||||
|
||||
it('should throw an error if tokenId is not included', () => {
|
||||
try {
|
||||
const orderData = { data: { messageType: 1, messageClass: 1 } }
|
||||
uut.validate(orderData)
|
||||
const data = { messageType: 1, messageClass: 1 }
|
||||
uut.validate(data)
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, "Property 'tokenId' must be a string.")
|
||||
@@ -72,11 +69,8 @@ describe('#Order-Entity', () => {
|
||||
|
||||
it('should throw an error if buyOrSell is not included', () => {
|
||||
try {
|
||||
const orderData = {
|
||||
data: { messageType: 1, messageClass: 1, tokenId: 'fakeId' }
|
||||
}
|
||||
|
||||
uut.validate(orderData)
|
||||
const data = { messageType: 1, messageClass: 1, tokenId: 'fakeId' }
|
||||
uut.validate(data)
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, "Property 'buyOrSell' must be a string.")
|
||||
@@ -85,16 +79,13 @@ describe('#Order-Entity', () => {
|
||||
|
||||
it('should throw an error if rateInSats is not included', () => {
|
||||
try {
|
||||
const orderData = {
|
||||
data: {
|
||||
const data = {
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'fakeId',
|
||||
buyOrSell: 'buy'
|
||||
}
|
||||
}
|
||||
|
||||
uut.validate(orderData)
|
||||
uut.validate(data)
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
@@ -106,17 +97,14 @@ describe('#Order-Entity', () => {
|
||||
|
||||
it('should throw an error if minSatsToExchange is not included', () => {
|
||||
try {
|
||||
const orderData = {
|
||||
data: {
|
||||
const data = {
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'fakeId',
|
||||
buyOrSell: 'buy',
|
||||
rateInSats: 1000
|
||||
}
|
||||
}
|
||||
|
||||
uut.validate(orderData)
|
||||
uut.validate(data)
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
@@ -128,8 +116,7 @@ describe('#Order-Entity', () => {
|
||||
|
||||
it('should throw an error if numTokens is not included', () => {
|
||||
try {
|
||||
const orderData = {
|
||||
data: {
|
||||
const data = {
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'fakeId',
|
||||
@@ -137,96 +124,11 @@ describe('#Order-Entity', () => {
|
||||
rateInSats: 1000,
|
||||
minSatsToExchange: 350
|
||||
}
|
||||
}
|
||||
uut.validate(orderData)
|
||||
uut.validate(data)
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, "Property 'numTokens' must be a number.")
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if utxoTxid is not included', () => {
|
||||
try {
|
||||
const orderData = {
|
||||
data: {
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'fakeId',
|
||||
buyOrSell: 'buy',
|
||||
rateInSats: 1000,
|
||||
minSatsToExchange: 350,
|
||||
numTokens: 1
|
||||
}
|
||||
}
|
||||
uut.validate(orderData)
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, "Property 'utxoTxid' must be a string.")
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if utxoVout is not included', () => {
|
||||
try {
|
||||
const orderData = {
|
||||
data: {
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'fakeId',
|
||||
buyOrSell: 'buy',
|
||||
rateInSats: 1000,
|
||||
minSatsToExchange: 350,
|
||||
numTokens: 1,
|
||||
utxoTxid: 'fakeTxid'
|
||||
}
|
||||
}
|
||||
uut.validate(orderData)
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
err.message,
|
||||
"Property 'utxoVout' must be an integer number."
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should validate a new order', () => {
|
||||
const orderObj = {
|
||||
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'
|
||||
}
|
||||
|
||||
const result = uut.validate(orderObj)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'messageType')
|
||||
assert.property(result, 'messageClass')
|
||||
assert.property(result, 'tokenId')
|
||||
assert.property(result, 'buyOrSell')
|
||||
assert.property(result, 'rateInSats')
|
||||
assert.property(result, 'minSatsToExchange')
|
||||
assert.property(result, 'numTokens')
|
||||
assert.property(result, 'utxoTxid')
|
||||
assert.property(result, 'utxoVout')
|
||||
assert.property(result, 'timestamp')
|
||||
assert.property(result, 'localTimestamp')
|
||||
assert.property(result, 'txid')
|
||||
assert.property(result, 'p2wdbHash')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -46,97 +46,78 @@ describe('#offer-use-case', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#ensureFunds', () => {
|
||||
it('should return true if wallet has enough funds for a sell order', async () => {
|
||||
const offerEntity = {
|
||||
lokadId: 'SWP',
|
||||
describe('#createOffer', () => {
|
||||
it('should ignore an offer if utxo has been spent', async () => {
|
||||
const offerObj = {
|
||||
appId: 'swapTest555',
|
||||
data: {
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
||||
tokenId:
|
||||
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
|
||||
buyOrSell: 'sell',
|
||||
rateInSats: 1000,
|
||||
minSatsToExchange: 0,
|
||||
numTokens: 1
|
||||
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'
|
||||
}
|
||||
|
||||
const result = await uut.ensureFunds(offerEntity)
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.adapters.bchjs.Blockchain, 'getTxOut').resolves(null)
|
||||
|
||||
const result = await uut.createOffer(offerObj)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result, false)
|
||||
})
|
||||
|
||||
it('should create an offer and return the hash', 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.bchjs.Blockchain, 'getTxOut').resolves({
|
||||
bestblock:
|
||||
'000000000000000000d2060b83f90f8187b92fcccb4a42aaa19ce5a305fe0ae3',
|
||||
confirmations: 0,
|
||||
value: 0,
|
||||
scriptPubKey: {
|
||||
asm: 'OP_RETURN 5262419 1 1145980243 38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0 00000000001e8480 0000000009a7ec80',
|
||||
hex: '6a04534c500001010453454e442038e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b00800000000001e8480080000000009a7ec80',
|
||||
type: 'nulldata'
|
||||
},
|
||||
coinbase: false
|
||||
})
|
||||
|
||||
const result = await uut.createOffer(offerObj)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#moveTokens', () => {
|
||||
it('should move tokens to the holding address', async () => {
|
||||
// Mock dependencies
|
||||
// sandbox
|
||||
// .stub(uut.adapters.wallet.bchWallet, 'sendTokens')
|
||||
// .resolves('fakeTxid')
|
||||
|
||||
const offerEntity = {
|
||||
lokadId: 'SWP',
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
||||
buyOrSell: 'sell',
|
||||
rateInSats: 1000,
|
||||
minSatsToExchange: 0,
|
||||
numTokens: 1
|
||||
}
|
||||
|
||||
const result = await uut.moveTokens(offerEntity)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'txid')
|
||||
assert.property(result, 'vout')
|
||||
|
||||
assert.equal(result.txid, 'fakeTxid')
|
||||
assert.equal(result.vout, 1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#createOffer', () => {
|
||||
it('should create an offer and return the hash', async () => {
|
||||
const entryObj = {
|
||||
lokadId: 'SWP',
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'token-id',
|
||||
buyOrSell: 'sell',
|
||||
rateInSats: 1000,
|
||||
minSatsToExchange: 1250,
|
||||
numTokens: 1
|
||||
}
|
||||
|
||||
// Mock dependencies
|
||||
// sandbox.stub(uut.adapters.wallet, 'burnPsf').resolves('fakeTxid')
|
||||
sandbox.stub(uut.offerEntity, 'validate').returns(entryObj)
|
||||
sandbox.stub(uut, 'ensureFunds').resolves()
|
||||
sandbox.stub(uut, 'moveTokens').resolves({ txid: 'fakeTxid', vout: 0, hdIndex: 1 })
|
||||
sandbox.stub(uut.adapters.wallet.bchWallet, 'getUtxos').resolves()
|
||||
// sandbox
|
||||
// .stub(uut.adapters.wallet, 'generateSignature')
|
||||
// .resolves('fakeSignature')
|
||||
sandbox.stub(uut.adapters.p2wdb, 'write').resolves('fakeHash')
|
||||
|
||||
const result = await uut.createOffer(entryObj)
|
||||
console.log('result: ', result)
|
||||
|
||||
assert.isString(result)
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.offerEntity, 'validate')
|
||||
.throws(new Error('test error'))
|
||||
|
||||
await uut.createOffer()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -46,78 +46,97 @@ describe('#order-use-case', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#createOrder', () => {
|
||||
it('should ignore an offer if utxo has been spent', async () => {
|
||||
const orderObj = {
|
||||
appId: 'swapTest555',
|
||||
data: {
|
||||
describe('#ensureFunds', () => {
|
||||
it('should return true if wallet has enough funds for a sell order', async () => {
|
||||
const orderEntity = {
|
||||
lokadId: 'SWP',
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId:
|
||||
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
|
||||
tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
||||
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'
|
||||
minSatsToExchange: 0,
|
||||
numTokens: 1
|
||||
}
|
||||
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.adapters.bchjs.Blockchain, 'getTxOut').resolves(null)
|
||||
|
||||
const result = await uut.createOrder(orderObj)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result, false)
|
||||
})
|
||||
|
||||
it('should create an offer and return the hash', async () => {
|
||||
const orderObj = {
|
||||
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.bchjs.Blockchain, 'getTxOut').resolves({
|
||||
bestblock:
|
||||
'000000000000000000d2060b83f90f8187b92fcccb4a42aaa19ce5a305fe0ae3',
|
||||
confirmations: 0,
|
||||
value: 0,
|
||||
scriptPubKey: {
|
||||
asm: 'OP_RETURN 5262419 1 1145980243 38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0 00000000001e8480 0000000009a7ec80',
|
||||
hex: '6a04534c500001010453454e442038e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b00800000000001e8480080000000009a7ec80',
|
||||
type: 'nulldata'
|
||||
},
|
||||
coinbase: false
|
||||
})
|
||||
|
||||
const result = await uut.createOrder(orderObj)
|
||||
// console.log('result: ', result)
|
||||
const result = await uut.ensureFunds(orderEntity)
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#moveTokens', () => {
|
||||
it('should move tokens to the holding address', async () => {
|
||||
// Mock dependencies
|
||||
// sandbox
|
||||
// .stub(uut.adapters.wallet.bchWallet, 'sendTokens')
|
||||
// .resolves('fakeTxid')
|
||||
|
||||
const orderEntity = {
|
||||
lokadId: 'SWP',
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
||||
buyOrSell: 'sell',
|
||||
rateInSats: 1000,
|
||||
minSatsToExchange: 0,
|
||||
numTokens: 1
|
||||
}
|
||||
|
||||
const result = await uut.moveTokens(orderEntity)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'txid')
|
||||
assert.property(result, 'vout')
|
||||
|
||||
assert.equal(result.txid, 'fakeTxid')
|
||||
assert.equal(result.vout, 1)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#createOrder', () => {
|
||||
it('should create an order and return the hash', async () => {
|
||||
const entryObj = {
|
||||
lokadId: 'SWP',
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'token-id',
|
||||
buyOrSell: 'sell',
|
||||
rateInSats: 1000,
|
||||
minSatsToExchange: 1250,
|
||||
numTokens: 1
|
||||
}
|
||||
|
||||
// Mock dependencies
|
||||
// sandbox.stub(uut.adapters.wallet, 'burnPsf').resolves('fakeTxid')
|
||||
sandbox.stub(uut.orderEntity, 'validate').returns(entryObj)
|
||||
sandbox.stub(uut, 'ensureFunds').resolves()
|
||||
sandbox.stub(uut, 'moveTokens').resolves({ txid: 'fakeTxid', vout: 0, hdIndex: 1 })
|
||||
sandbox.stub(uut.adapters.wallet.bchWallet, 'getUtxos').resolves()
|
||||
// sandbox
|
||||
// .stub(uut.adapters.wallet, 'generateSignature')
|
||||
// .resolves('fakeSignature')
|
||||
sandbox.stub(uut.adapters.p2wdb, 'write').resolves('fakeHash')
|
||||
|
||||
const result = await uut.createOrder(entryObj)
|
||||
console.log('result: ', result)
|
||||
|
||||
assert.isString(result)
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.orderEntity, 'validate')
|
||||
.throws(new Error('test error'))
|
||||
|
||||
await uut.createOrder()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user