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 {
|
||||||
try {
|
try {
|
||||||
// Delete an old webhook if it exists.
|
// 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) {
|
} catch (err) {
|
||||||
/* exit quietly */
|
/* exit quietly */
|
||||||
// console.log('err deleting webhook: ', err)
|
// 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')
|
console.log('Webhook created')
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log('Webhook cant be created')
|
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 mongoose = require('mongoose')
|
||||||
|
|
||||||
const Order = new mongoose.Schema({
|
const Order = new mongoose.Schema({
|
||||||
@@ -27,7 +33,6 @@ const Order = new mongoose.Schema({
|
|||||||
lokadId: { type: String },
|
lokadId: { type: String },
|
||||||
messageType: { type: Number },
|
messageType: { type: Number },
|
||||||
messageClass: { type: Number }
|
messageClass: { type: Number }
|
||||||
|
|
||||||
})
|
})
|
||||||
|
|
||||||
module.exports = mongoose.model('order', Order)
|
module.exports = mongoose.model('order', Order)
|
||||||
|
|||||||
@@ -32,13 +32,15 @@ class OfferRESTControllerLib {
|
|||||||
// No api-doc documentation because this wont be a public endpoint
|
// No api-doc documentation because this wont be a public endpoint
|
||||||
async createOffer (ctx) {
|
async createOffer (ctx) {
|
||||||
try {
|
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) {
|
} catch (err) {
|
||||||
// console.log(`err.message: ${err.message}`)
|
// console.log(`err.message: ${err.message}`)
|
||||||
// console.log('err: ', err)
|
// 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
|
// DRY error handler
|
||||||
handleError (ctx, err) {
|
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 an HTTP status is specified by the buisiness logic, use that.
|
||||||
if (err.status) {
|
if (err.status) {
|
||||||
if (err.message) {
|
if (err.message) {
|
||||||
|
|||||||
@@ -50,6 +50,8 @@ class OfferRouter {
|
|||||||
|
|
||||||
// Define the routes and attach the controller.
|
// Define the routes and attach the controller.
|
||||||
this.router.post('/', _this.offerRESTController.createOffer)
|
this.router.post('/', _this.offerRESTController.createOffer)
|
||||||
|
this.router.get('/list', _this.offerRESTController.listOffers)
|
||||||
|
this.router.post('/take', _this.offerRESTController.takeOffer)
|
||||||
|
|
||||||
// Attach the Controller routes to the Koa app.
|
// Attach the Controller routes to the Koa app.
|
||||||
app.use(_this.router.routes())
|
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')
|
// const { wlogger } = require('../../../adapters/wlogger')
|
||||||
@@ -32,15 +32,13 @@ class OrderRESTControllerLib {
|
|||||||
// No api-doc documentation because this wont be a public endpoint
|
// No api-doc documentation because this wont be a public endpoint
|
||||||
async createOrder (ctx) {
|
async createOrder (ctx) {
|
||||||
try {
|
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 = {
|
ctx.body = { hash }
|
||||||
success: true
|
|
||||||
}
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.log(`err.message: ${err.message}`)
|
// console.log(`err.message: ${err.message}`)
|
||||||
// console.log('err: ', err)
|
// 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
|
// DRY error handler
|
||||||
handleError (ctx, err) {
|
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 an HTTP status is specified by the buisiness logic, use that.
|
||||||
if (err.status) {
|
if (err.status) {
|
||||||
if (err.message) {
|
if (err.message) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/*
|
/*
|
||||||
REST API library for /offer route.
|
REST API library for /order route.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Public npm libraries.
|
// Public npm libraries.
|
||||||
@@ -50,8 +50,6 @@ class OrderRouter {
|
|||||||
|
|
||||||
// Define the routes and attach the controller.
|
// Define the routes and attach the controller.
|
||||||
this.router.post('/', _this.orderRESTController.createOrder)
|
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.
|
// Attach the Controller routes to the Koa app.
|
||||||
app.use(_this.router.routes())
|
app.use(_this.router.routes())
|
||||||
|
|||||||
+37
-20
@@ -1,22 +1,23 @@
|
|||||||
/*
|
/*
|
||||||
Offer Entity
|
Offer Entity
|
||||||
An Offer Entity is nearly the same as an Order. But while an Order is generated
|
An ffer is created when a new Signal is detected via the P2WDB webhook.
|
||||||
by a webhook from P2WDB, the Offer Entity is created internally. It is used
|
It's destroyed when the UTXO described in the Signal has been detected as spent.
|
||||||
to track an Order generated by this application.
|
|
||||||
|
|
||||||
The Offer tracks the hdIndex address used to hold tokens or BCH for sale.
|
|
||||||
*/
|
*/
|
||||||
class Offer {
|
|
||||||
validate (data) {
|
class OfferEntity {
|
||||||
const {
|
constructor () {
|
||||||
messageType,
|
this.orderStatus = ['posted', 'taken', 'completed']
|
||||||
messageClass,
|
}
|
||||||
tokenId,
|
|
||||||
buyOrSell,
|
validate (orderData = {}) {
|
||||||
rateInSats,
|
// Throw an error if input object does not have a data property
|
||||||
minSatsToExchange,
|
if (!orderData.data) {
|
||||||
numTokens
|
throw new Error(
|
||||||
} = data
|
'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
|
// Input Validation
|
||||||
if (!messageType || typeof messageType !== 'number') {
|
if (!messageType || typeof messageType !== 'number') {
|
||||||
@@ -40,19 +41,35 @@ class Offer {
|
|||||||
if (!numTokens || typeof numTokens !== 'number') {
|
if (!numTokens || typeof numTokens !== 'number') {
|
||||||
throw new Error("Property 'numTokens' must be a 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,
|
messageType,
|
||||||
messageClass,
|
messageClass,
|
||||||
tokenId,
|
tokenId,
|
||||||
buyOrSell,
|
buyOrSell,
|
||||||
rateInSats,
|
rateInSats,
|
||||||
minSatsToExchange,
|
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
|
Order Entity
|
||||||
An order is created when a new Signal is detected via the P2WDB webhook.
|
An Order Entity is nearly the same as an Offer. But while an Offer is generated
|
||||||
It's destroyed when the UTXO described in the Signal has been detected as spent.
|
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 {
|
class Order {
|
||||||
constructor () {
|
validate (data) {
|
||||||
this.orderStatus = ['posted', 'taken', 'completed']
|
const {
|
||||||
}
|
messageType,
|
||||||
|
messageClass,
|
||||||
validate (orderData = {}) {
|
tokenId,
|
||||||
// Throw an error if input object does not have a data property
|
buyOrSell,
|
||||||
if (!orderData.data) {
|
rateInSats,
|
||||||
throw new Error(
|
minSatsToExchange,
|
||||||
'Input to order.validate() must be an object with a data property.'
|
numTokens
|
||||||
)
|
} = data
|
||||||
}
|
|
||||||
|
|
||||||
const { messageType, messageClass, tokenId, buyOrSell, rateInSats, minSatsToExchange, numTokens, utxoTxid, utxoVout, orderStatus } = orderData.data
|
|
||||||
|
|
||||||
// Input Validation
|
// Input Validation
|
||||||
if (!messageType || typeof messageType !== 'number') {
|
if (!messageType || typeof messageType !== 'number') {
|
||||||
@@ -40,35 +40,19 @@ class OrderEntity {
|
|||||||
if (!numTokens || typeof numTokens !== 'number') {
|
if (!numTokens || typeof numTokens !== 'number') {
|
||||||
throw new Error("Property 'numTokens' must be a 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,
|
messageType,
|
||||||
messageClass,
|
messageClass,
|
||||||
tokenId,
|
tokenId,
|
||||||
buyOrSell,
|
buyOrSell,
|
||||||
rateInSats,
|
rateInSats,
|
||||||
minSatsToExchange,
|
minSatsToExchange,
|
||||||
numTokens,
|
numTokens
|
||||||
utxoTxid,
|
|
||||||
utxoVout,
|
|
||||||
timestamp: orderData.timestamp,
|
|
||||||
localTimestamp: orderData.localTimeStamp,
|
|
||||||
txid: orderData.txid,
|
|
||||||
p2wdbHash: orderData.hash,
|
|
||||||
orderStatus: orderStatus || this.orderStatus[0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return validatedOrderData
|
return offerData
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = OrderEntity
|
module.exports = Order
|
||||||
|
|||||||
@@ -1,46 +1,46 @@
|
|||||||
/*
|
/*
|
||||||
Use Case library for Orders.
|
Use Case library for Offers.
|
||||||
Orders are created by a webhook trigger from the P2WDB. Orders are a result of
|
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
|
new data in P2WDB. They differ from Offers, which are generated by a local
|
||||||
user.
|
user.
|
||||||
An Order is created to match a local Offer, but it's created indirectly, as
|
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, Orders generated from
|
a response to the webhook from the P2WDB. In this way, Offers generated from
|
||||||
local Offers are no different than Orders generated by other peers.
|
local Offers are no different than Offers generated by other peers.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Local libraries
|
// Local libraries
|
||||||
const OrderEntity = require('../../entities/order')
|
const OfferEntity = require('../../entities/offer')
|
||||||
const config = require('../../../config')
|
const config = require('../../../config')
|
||||||
|
|
||||||
class OrderUseCases {
|
class OfferUseCases {
|
||||||
constructor (localConfig = {}) {
|
constructor (localConfig = {}) {
|
||||||
// console.log('User localConfig: ', localConfig)
|
// console.log('User localConfig: ', localConfig)
|
||||||
this.adapters = localConfig.adapters
|
this.adapters = localConfig.adapters
|
||||||
if (!this.adapters) {
|
if (!this.adapters) {
|
||||||
throw new Error(
|
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
|
// Encapsulate dependencies
|
||||||
this.config = config
|
this.config = config
|
||||||
|
|
||||||
this.orderEntity = new OrderEntity()
|
this.offerEntity = new OfferEntity()
|
||||||
this.OrderModel = this.adapters.localdb.Order
|
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.
|
// triggered by a P2WDB webhook.
|
||||||
async createOrder (orderObj) {
|
async createOffer (offerObj) {
|
||||||
try {
|
try {
|
||||||
console.log('Use Case createOrder(orderObj): ', orderObj)
|
console.log('Use Case createOffer(offerObj): ', offerObj)
|
||||||
|
|
||||||
// console.log('this.adapters.bchjs: ', this.adapters.bchjs)
|
// console.log('this.adapters.bchjs: ', this.adapters.bchjs)
|
||||||
|
|
||||||
// Verify that UTXO in order is unspent. If it is spent, then ignore the
|
// Verify that UTXO in offer is unspent. If it is spent, then ignore the
|
||||||
// order.
|
// offer.
|
||||||
const txid = orderObj.data.utxoTxid
|
const txid = offerObj.data.utxoTxid
|
||||||
const vout = orderObj.data.utxoVout
|
const vout = offerObj.data.utxoVout
|
||||||
const utxoStatus = await this.adapters.bchjs.Blockchain.getTxOut(
|
const utxoStatus = await this.adapters.bchjs.Blockchain.getTxOut(
|
||||||
txid,
|
txid,
|
||||||
vout
|
vout
|
||||||
@@ -48,51 +48,51 @@ class OrderUseCases {
|
|||||||
console.log('utxoStatus: ', utxoStatus)
|
console.log('utxoStatus: ', utxoStatus)
|
||||||
if (utxoStatus === null) return false
|
if (utxoStatus === null) return false
|
||||||
|
|
||||||
// A new order gets a status of 'posted'
|
// A new offer gets a status of 'posted'
|
||||||
orderObj.data.orderStatus = 'posted'
|
offerObj.data.offerStatus = 'posted'
|
||||||
|
|
||||||
const orderEntity = this.orderEntity.validate(orderObj)
|
const offerEntity = this.offerEntity.validate(offerObj)
|
||||||
console.log('orderEntity: ', orderEntity)
|
console.log('offerEntity: ', offerEntity)
|
||||||
|
|
||||||
// Add order to the local database.
|
// Add offer to the local database.
|
||||||
const orderModel = new this.OrderModel(orderEntity)
|
const offerModel = new this.OfferModel(offerEntity)
|
||||||
await orderModel.save()
|
await offerModel.save()
|
||||||
|
|
||||||
return true
|
return true
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error in createOrder()')
|
console.error('Error in createOffer()')
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async listOrders () {
|
async listOffers () {
|
||||||
try {
|
try {
|
||||||
return this.OrderModel.find({})
|
return this.OfferModel.find({})
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error in use-cases/order/listOrders()')
|
console.error('Error in use-cases/offer/listOffers()')
|
||||||
throw error
|
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:
|
// 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
|
// 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 {
|
try {
|
||||||
console.log('orderCid: ', orderCid)
|
console.log('offerCid: ', offerCid)
|
||||||
|
|
||||||
// Get the Order information
|
// Get the Offer information
|
||||||
const orderInfo = await this.findOrderByHash(orderCid)
|
const offerInfo = await this.findOfferByHash(offerCid)
|
||||||
console.log(`orderInfo: ${JSON.stringify(orderInfo, null, 2)}`)
|
console.log(`offerInfo: ${JSON.stringify(offerInfo, null, 2)}`)
|
||||||
|
|
||||||
// Ensure the order is in a 'posted' state and not already 'taken'
|
// Ensure the offer is in a 'posted' state and not already 'taken'
|
||||||
if (orderInfo.orderStatus && orderInfo.orderStatus !== 'posted') {
|
if (offerInfo.offerStatus && offerInfo.offerStatus !== 'posted') {
|
||||||
throw new Error('order already taken')
|
throw new Error('offer already taken')
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verify that UTXO for sale is unspent. Abort if it's been spent.
|
// Verify that UTXO for sale is unspent. Abort if it's been spent.
|
||||||
const txid = orderInfo.utxoTxid
|
const txid = offerInfo.utxoTxid
|
||||||
const vout = orderInfo.utxoVout
|
const vout = offerInfo.utxoVout
|
||||||
const utxoStatus = await this.adapters.bchjs.Blockchain.getTxOut(
|
const utxoStatus = await this.adapters.bchjs.Blockchain.getTxOut(
|
||||||
txid,
|
txid,
|
||||||
vout
|
vout
|
||||||
@@ -104,32 +104,32 @@ class OrderUseCases {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Ensure the app has enough funds to complete the trade.
|
// Ensure the app has enough funds to complete the trade.
|
||||||
await this.ensureFunds(orderInfo)
|
await this.ensureFunds(offerInfo)
|
||||||
|
|
||||||
// Get UTXOs.
|
// Get UTXOs.
|
||||||
const utxos = this.adapters.wallet.bchWallet.utxos.utxoStore
|
const utxos = this.adapters.wallet.bchWallet.utxos.utxoStore
|
||||||
console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
|
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.
|
// 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
|
// 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
|
// return partialTxHex
|
||||||
|
|
||||||
// Create valid Order object
|
// Create valid Offer object
|
||||||
const takenOrderInfo = Object.assign({}, orderInfo)
|
const takenOfferInfo = Object.assign({}, offerInfo)
|
||||||
takenOrderInfo.patialTxHex = partialTxHex
|
takenOfferInfo.patialTxHex = partialTxHex
|
||||||
delete takenOrderInfo.p2wdbHash
|
delete takenOfferInfo.p2wdbHash
|
||||||
delete takenOrderInfo._id
|
delete takenOfferInfo._id
|
||||||
takenOrderInfo.offerHash = orderInfo.p2wdbHash
|
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
|
// 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 = {
|
const p2wdbObj = {
|
||||||
wif: this.adapters.wallet.bchWallet.walletInfo.privateKey,
|
wif: this.adapters.wallet.bchWallet.walletInfo.privateKey,
|
||||||
data: takenOrderInfo,
|
data: takenOfferInfo,
|
||||||
appId: this.config.p2wdbAppId
|
appId: this.config.p2wdbAppId
|
||||||
}
|
}
|
||||||
const hash = await this.adapters.p2wdb.write(p2wdbObj)
|
const hash = await this.adapters.p2wdb.write(p2wdbObj)
|
||||||
@@ -137,14 +137,14 @@ class OrderUseCases {
|
|||||||
// Return the P2WDB CID
|
// Return the P2WDB CID
|
||||||
return hash
|
return hash
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error in use-cases/order/takeOrder(): ', err)
|
console.error('Error in use-cases/offer/takeOffer(): ', err)
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure that the wallet has enough BCH and tokens to complete the requested
|
// 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.
|
// trade. Will return true if it does. Will throw an error if it doesn't.
|
||||||
async ensureFunds (orderEntity) {
|
async ensureFunds (offerEntity) {
|
||||||
try {
|
try {
|
||||||
// console.log('this.adapters.wallet: ', this.adapters.wallet.bchWallet)
|
// console.log('this.adapters.wallet: ', this.adapters.wallet.bchWallet)
|
||||||
// console.log(`walletInfo: ${JSON.stringify(this.adapters.wallet.bchWallet.walletInfo, null, 2)}`)
|
// 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)
|
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 (!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
|
// Sell Offer
|
||||||
|
|
||||||
// Ensure the app wallet controlls enough BCH to pay for the tokens.
|
// 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()
|
const balance = await this.adapters.wallet.bchWallet.getBalance()
|
||||||
console.log(`wallet balance: ${balance}, sats needed: ${satsNeeded}`)
|
console.log(`wallet balance: ${balance}, sats needed: ${satsNeeded}`)
|
||||||
const SATS_MARGIN = 5000
|
const SATS_MARGIN = 5000
|
||||||
@@ -169,7 +169,7 @@ class OrderUseCases {
|
|||||||
//
|
//
|
||||||
} else {
|
} else {
|
||||||
// Buy Offer
|
// Buy Offer
|
||||||
throw new Error('Buy orders are not supported yet.')
|
throw new Error('Buy offers are not supported yet.')
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true
|
||||||
@@ -179,27 +179,27 @@ class OrderUseCases {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async findOrderByHash (p2wdbHash) {
|
async findOfferByHash (p2wdbHash) {
|
||||||
try {
|
try {
|
||||||
if (typeof p2wdbHash !== 'string' || !p2wdbHash) {
|
if (typeof p2wdbHash !== 'string' || !p2wdbHash) {
|
||||||
throw new Error('p2wdbHash must be a string')
|
throw new Error('p2wdbHash must be a string')
|
||||||
}
|
}
|
||||||
|
|
||||||
const order = await this.OrderModel.findOne({ p2wdbHash })
|
const offer = await this.OfferModel.findOne({ p2wdbHash })
|
||||||
|
|
||||||
if (!order) {
|
if (!offer) {
|
||||||
throw new Error('order not found')
|
throw new Error('offer not found')
|
||||||
}
|
}
|
||||||
|
|
||||||
const orderObject = order.toObject()
|
const offerObject = offer.toObject()
|
||||||
// return this.orderEntity.validateFromModel(orderObject)
|
// return this.offerEntity.validateFromModel(offerObject)
|
||||||
|
|
||||||
return orderObject
|
return offerObject
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Error in findOrder(): ', err)
|
console.error('Error in findOffer(): ', err)
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = OrderUseCases
|
module.exports = OfferUseCases
|
||||||
@@ -1,89 +1,89 @@
|
|||||||
/*
|
/*
|
||||||
Offer use-case library.
|
Order use-case library.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// Local libraries
|
// Local libraries
|
||||||
const { wlogger } = require('../adapters/wlogger')
|
const { wlogger } = require('../adapters/wlogger')
|
||||||
const OfferEntity = require('../entities/offer')
|
const OrderEntity = require('../entities/order')
|
||||||
const config = require('../../config')
|
const config = require('../../config')
|
||||||
|
|
||||||
class OfferLib {
|
class OrderLib {
|
||||||
constructor (localConfig = {}) {
|
constructor (localConfig = {}) {
|
||||||
// console.log('User localConfig: ', localConfig)
|
// console.log('User localConfig: ', localConfig)
|
||||||
this.adapters = localConfig.adapters
|
this.adapters = localConfig.adapters
|
||||||
if (!this.adapters) {
|
if (!this.adapters) {
|
||||||
throw new Error(
|
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
|
// Encapsulate dependencies
|
||||||
this.offerEntity = new OfferEntity()
|
this.orderEntity = new OrderEntity()
|
||||||
this.OfferModel = this.adapters.localdb.Offer
|
this.OrderModel = this.adapters.localdb.Order
|
||||||
this.bch = this.adapters.bch
|
this.bch = this.adapters.bch
|
||||||
this.config = config
|
this.config = config
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a new offer model and add it to the Mongo database.
|
// Create a new order model and add it to the Mongo database.
|
||||||
async createOffer (entryObj) {
|
async createOrder (entryObj) {
|
||||||
try {
|
try {
|
||||||
// console.log('createOffer(entryObj): ', entryObj)
|
// console.log('createOrder(entryObj): ', entryObj)
|
||||||
|
|
||||||
// Input Validation
|
// Input Validation
|
||||||
const offerEntity = this.offerEntity.validate(entryObj)
|
const orderEntity = this.orderEntity.validate(entryObj)
|
||||||
console.log('offerEntity: ', offerEntity)
|
console.log('orderEntity: ', orderEntity)
|
||||||
|
|
||||||
// Ensure sufficient tokens exist to create the offer.
|
// Ensure sufficient tokens exist to create the order.
|
||||||
await this.ensureFunds(offerEntity)
|
await this.ensureFunds(orderEntity)
|
||||||
|
|
||||||
// Move the tokens to holding address.
|
// Move the tokens to holding address.
|
||||||
const utxoInfo = await this.moveTokens(offerEntity)
|
const utxoInfo = await this.moveTokens(orderEntity)
|
||||||
console.log('utxoInfo: ', utxoInfo)
|
console.log('utxoInfo: ', utxoInfo)
|
||||||
|
|
||||||
// Update the UTXO store for the wallet.
|
// Update the UTXO store for the wallet.
|
||||||
await this.adapters.wallet.bchWallet.bchjs.Util.sleep(3000)
|
await this.adapters.wallet.bchWallet.bchjs.Util.sleep(3000)
|
||||||
await this.adapters.wallet.bchWallet.getUtxos()
|
await this.adapters.wallet.bchWallet.getUtxos()
|
||||||
|
|
||||||
// Update the offer with the new UTXO information.
|
// Update the order with the new UTXO information.
|
||||||
offerEntity.utxoTxid = utxoInfo.txid
|
orderEntity.utxoTxid = utxoInfo.txid
|
||||||
offerEntity.utxoVout = utxoInfo.vout
|
orderEntity.utxoVout = utxoInfo.vout
|
||||||
offerEntity.hdIndex = utxoInfo.hdIndex
|
orderEntity.hdIndex = utxoInfo.hdIndex
|
||||||
|
|
||||||
// Add offer to P2WDB.
|
// Add order to P2WDB.
|
||||||
const p2wdbObj = {
|
const p2wdbObj = {
|
||||||
wif: this.adapters.wallet.bchWallet.walletInfo.privateKey,
|
wif: this.adapters.wallet.bchWallet.walletInfo.privateKey,
|
||||||
data: offerEntity,
|
data: orderEntity,
|
||||||
appId: this.config.p2wdbAppId
|
appId: this.config.p2wdbAppId
|
||||||
}
|
}
|
||||||
const hash = await this.adapters.p2wdb.write(p2wdbObj)
|
const hash = await this.adapters.p2wdb.write(p2wdbObj)
|
||||||
// console.log('hash: ', hash)
|
// console.log('hash: ', hash)
|
||||||
|
|
||||||
// Create a MongoDB model to hold the Offer
|
// Create a MongoDB model to hold the Order
|
||||||
offerEntity.p2wdbHash = hash
|
orderEntity.p2wdbHash = hash
|
||||||
console.log(`creating new offer model: ${JSON.stringify(offerEntity, null, 2)}`)
|
console.log(`creating new order model: ${JSON.stringify(orderEntity, null, 2)}`)
|
||||||
const offer = new this.OfferModel(offerEntity)
|
const order = new this.OrderModel(orderEntity)
|
||||||
await offer.save()
|
await order.save()
|
||||||
|
|
||||||
return hash
|
return hash
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.log("Error in use-cases/entry.js/createEntry()", err.message)
|
// 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
|
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
|
// This will generate the UTXO used in the Signal message. This function
|
||||||
// moves the funds and returns the UTXO information.
|
// moves the funds and returns the UTXO information.
|
||||||
async moveTokens (offerEntity) {
|
async moveTokens (orderEntity) {
|
||||||
try {
|
try {
|
||||||
const keyPair = await this.adapters.wallet.getKeyPair()
|
const keyPair = await this.adapters.wallet.getKeyPair()
|
||||||
console.log('keyPair: ', keyPair)
|
console.log('keyPair: ', keyPair)
|
||||||
|
|
||||||
const receiver = {
|
const receiver = {
|
||||||
address: keyPair.cashAddress,
|
address: keyPair.cashAddress,
|
||||||
tokenId: offerEntity.tokenId,
|
tokenId: orderEntity.tokenId,
|
||||||
qty: offerEntity.numTokens
|
qty: orderEntity.numTokens
|
||||||
}
|
}
|
||||||
|
|
||||||
const txid = await this.adapters.wallet.bchWallet.sendTokens(receiver, 3)
|
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
|
// Ensure that the wallet has enough BCH and tokens to complete the requested
|
||||||
// trade.
|
// trade.
|
||||||
async ensureFunds (offerEntity) {
|
async ensureFunds (orderEntity) {
|
||||||
try {
|
try {
|
||||||
// console.log('this.adapters.wallet: ', this.adapters.wallet.bchWallet)
|
// console.log('this.adapters.wallet: ', this.adapters.wallet.bchWallet)
|
||||||
// console.log(`walletInfo: ${JSON.stringify(this.adapters.wallet.bchWallet.walletInfo, null, 2)}`)
|
// 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
|
const utxos = this.adapters.wallet.bchWallet.utxos.utxoStore
|
||||||
console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
|
console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
|
||||||
|
|
||||||
if (offerEntity.buyOrSell.includes('sell')) {
|
if (orderEntity.buyOrSell.includes('sell')) {
|
||||||
// Sell Offer
|
// 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(
|
const tokenUtxos = utxos.slpUtxos.type1.tokens.filter(
|
||||||
x => x.tokenId === offerEntity.tokenId
|
x => x.tokenId === orderEntity.tokenId
|
||||||
)
|
)
|
||||||
// console.log('tokenUtxos: ', tokenUtxos)
|
// console.log('tokenUtxos: ', tokenUtxos)
|
||||||
|
|
||||||
// Get the total amount of tokens in the wallet that match the token
|
// Get the total amount of tokens in the wallet that match the token
|
||||||
// in the offer.
|
// in the order.
|
||||||
let totalTokenBalance = 0
|
let totalTokenBalance = 0
|
||||||
tokenUtxos.map(x => (totalTokenBalance += parseFloat(x.qtyStr)))
|
tokenUtxos.map(x => (totalTokenBalance += parseFloat(x.qtyStr)))
|
||||||
console.log('totalTokenBalance: ', totalTokenBalance)
|
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.
|
// throw an error.
|
||||||
if (totalTokenBalance <= offerEntity.numTokens || isNaN(totalTokenBalance)) {
|
if (totalTokenBalance <= orderEntity.numTokens || isNaN(totalTokenBalance)) {
|
||||||
throw new Error(
|
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 {
|
} else {
|
||||||
// Buy Offer
|
// Buy Order
|
||||||
throw new Error('Buy orders are not supported yet.')
|
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.
|
Ensure the REST API is up an running before running this test.
|
||||||
*/
|
*/
|
||||||
@@ -10,7 +11,7 @@ const LOCALHOST = 'http://localhost:5700'
|
|||||||
|
|
||||||
async function start () {
|
async function start () {
|
||||||
try {
|
try {
|
||||||
const mockOffer = {
|
const mockOrder = {
|
||||||
lokadId: 'SWP',
|
lokadId: 'SWP',
|
||||||
messageType: 1,
|
messageType: 1,
|
||||||
messageClass: 1,
|
messageClass: 1,
|
||||||
@@ -24,8 +25,8 @@ async function start () {
|
|||||||
|
|
||||||
const options = {
|
const options = {
|
||||||
method: 'post',
|
method: 'post',
|
||||||
url: `${LOCALHOST}/offer`,
|
url: `${LOCALHOST}/order`,
|
||||||
data: { offer: mockOffer }
|
data: { order: mockOrder }
|
||||||
}
|
}
|
||||||
|
|
||||||
const result = await axios(options)
|
const result = await axios(options)
|
||||||
|
|||||||
@@ -64,36 +64,53 @@ describe('#Offer-REST-Router', () => {
|
|||||||
describe('#createOffer', () => {
|
describe('#createOffer', () => {
|
||||||
it('should create a new offer', async () => {
|
it('should create a new offer', async () => {
|
||||||
ctx.request.body = {
|
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
|
// Mock dependencies
|
||||||
sandbox.stub(uut.useCases.offer, 'createOffer').resolves('testHash')
|
// sandbox.stub(uut.useCases.offer, 'createOffer').resolves()
|
||||||
|
|
||||||
await uut.createOffer(ctx)
|
await uut.createOffer(ctx)
|
||||||
|
|
||||||
assert.equal(ctx.body.hash, 'testHash')
|
// assert.equal(ctx.body.hash, 'testHash')
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should catch and throw an error', async () => {
|
// it('should catch and throw an error', async () => {
|
||||||
try {
|
// try {
|
||||||
ctx.request.body = {
|
// ctx.request.body = {
|
||||||
offer: {}
|
// offer: {}
|
||||||
}
|
// }
|
||||||
|
//
|
||||||
// Force an error
|
// // Force an error
|
||||||
sandbox
|
// sandbox
|
||||||
.stub(uut.useCases.offer, 'createOffer')
|
// .stub(uut.useCases.offer, 'createOffer')
|
||||||
.rejects(new Error('test error'))
|
// .rejects(new Error('test error'))
|
||||||
|
//
|
||||||
await uut.createOffer(ctx)
|
// await uut.createOffer(ctx)
|
||||||
|
//
|
||||||
assert.fail('Unexpected code path')
|
// assert.fail('Unexpected code path')
|
||||||
} catch (err) {
|
// } catch (err) {
|
||||||
// console.log('err: ', err)
|
// // console.log('err: ', err)
|
||||||
assert.include(err.message, 'test error')
|
// assert.include(err.message, 'test error')
|
||||||
}
|
// }
|
||||||
})
|
// })
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#handleError', () => {
|
describe('#handleError', () => {
|
||||||
|
|||||||
@@ -64,53 +64,36 @@ describe('#Order-REST-Router', () => {
|
|||||||
describe('#createOrder', () => {
|
describe('#createOrder', () => {
|
||||||
it('should create a new order', async () => {
|
it('should create a new order', async () => {
|
||||||
ctx.request.body = {
|
ctx.request.body = {
|
||||||
appId: 'swapTest555',
|
order: {}
|
||||||
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
|
// Mock dependencies
|
||||||
// sandbox.stub(uut.useCases.order, 'createOrder').resolves()
|
sandbox.stub(uut.useCases.order, 'createOrder').resolves('testHash')
|
||||||
|
|
||||||
await uut.createOrder(ctx)
|
await uut.createOrder(ctx)
|
||||||
|
|
||||||
// assert.equal(ctx.body.hash, 'testHash')
|
assert.equal(ctx.body.hash, 'testHash')
|
||||||
})
|
})
|
||||||
|
|
||||||
// it('should catch and throw an error', async () => {
|
it('should catch and throw an error', async () => {
|
||||||
// try {
|
try {
|
||||||
// ctx.request.body = {
|
ctx.request.body = {
|
||||||
// order: {}
|
order: {}
|
||||||
// }
|
}
|
||||||
//
|
|
||||||
// // Force an error
|
// Force an error
|
||||||
// sandbox
|
sandbox
|
||||||
// .stub(uut.useCases.order, 'createOrder')
|
.stub(uut.useCases.order, 'createOrder')
|
||||||
// .rejects(new Error('test error'))
|
.rejects(new Error('test error'))
|
||||||
//
|
|
||||||
// await uut.createOrder(ctx)
|
await uut.createOrder(ctx)
|
||||||
//
|
|
||||||
// assert.fail('Unexpected code path')
|
assert.fail('Unexpected code path')
|
||||||
// } catch (err) {
|
} catch (err) {
|
||||||
// // console.log('err: ', err)
|
// console.log('err: ', err)
|
||||||
// assert.include(err.message, 'test error')
|
assert.include(err.message, 'test error')
|
||||||
// }
|
}
|
||||||
// })
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('#handleError', () => {
|
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
|
const assert = require('chai').assert
|
||||||
@@ -27,14 +27,17 @@ describe('#Offer-Entity', () => {
|
|||||||
uut.validate()
|
uut.validate()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.log(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', () => {
|
it('should throw an error if messageType is not included', () => {
|
||||||
try {
|
try {
|
||||||
const data = {}
|
const orderData = { data: {} }
|
||||||
uut.validate(data)
|
uut.validate(orderData)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.log(err)
|
// console.log(err)
|
||||||
assert.include(
|
assert.include(
|
||||||
@@ -46,8 +49,8 @@ describe('#Offer-Entity', () => {
|
|||||||
|
|
||||||
it('should throw an error if messageClass is not included', () => {
|
it('should throw an error if messageClass is not included', () => {
|
||||||
try {
|
try {
|
||||||
const data = { messageType: 1 }
|
const orderData = { data: { messageType: 1 } }
|
||||||
uut.validate(data)
|
uut.validate(orderData)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.log(err)
|
// console.log(err)
|
||||||
assert.include(
|
assert.include(
|
||||||
@@ -59,8 +62,8 @@ describe('#Offer-Entity', () => {
|
|||||||
|
|
||||||
it('should throw an error if tokenId is not included', () => {
|
it('should throw an error if tokenId is not included', () => {
|
||||||
try {
|
try {
|
||||||
const data = { messageType: 1, messageClass: 1 }
|
const orderData = { data: { messageType: 1, messageClass: 1 } }
|
||||||
uut.validate(data)
|
uut.validate(orderData)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.log(err)
|
// console.log(err)
|
||||||
assert.include(err.message, "Property 'tokenId' must be a string.")
|
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', () => {
|
it('should throw an error if buyOrSell is not included', () => {
|
||||||
try {
|
try {
|
||||||
const data = { messageType: 1, messageClass: 1, tokenId: 'fakeId' }
|
const orderData = {
|
||||||
uut.validate(data)
|
data: { messageType: 1, messageClass: 1, tokenId: 'fakeId' }
|
||||||
|
}
|
||||||
|
|
||||||
|
uut.validate(orderData)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.log(err)
|
// console.log(err)
|
||||||
assert.include(err.message, "Property 'buyOrSell' must be a string.")
|
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', () => {
|
it('should throw an error if rateInSats is not included', () => {
|
||||||
try {
|
try {
|
||||||
const data = {
|
const orderData = {
|
||||||
messageType: 1,
|
data: {
|
||||||
messageClass: 1,
|
messageType: 1,
|
||||||
tokenId: 'fakeId',
|
messageClass: 1,
|
||||||
buyOrSell: 'buy'
|
tokenId: 'fakeId',
|
||||||
|
buyOrSell: 'buy'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
uut.validate(data)
|
|
||||||
|
uut.validate(orderData)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.log(err)
|
// console.log(err)
|
||||||
assert.include(
|
assert.include(
|
||||||
@@ -97,14 +106,17 @@ describe('#Offer-Entity', () => {
|
|||||||
|
|
||||||
it('should throw an error if minSatsToExchange is not included', () => {
|
it('should throw an error if minSatsToExchange is not included', () => {
|
||||||
try {
|
try {
|
||||||
const data = {
|
const orderData = {
|
||||||
messageType: 1,
|
data: {
|
||||||
messageClass: 1,
|
messageType: 1,
|
||||||
tokenId: 'fakeId',
|
messageClass: 1,
|
||||||
buyOrSell: 'buy',
|
tokenId: 'fakeId',
|
||||||
rateInSats: 1000
|
buyOrSell: 'buy',
|
||||||
|
rateInSats: 1000
|
||||||
|
}
|
||||||
}
|
}
|
||||||
uut.validate(data)
|
|
||||||
|
uut.validate(orderData)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.log(err)
|
// console.log(err)
|
||||||
assert.include(
|
assert.include(
|
||||||
@@ -116,19 +128,105 @@ describe('#Offer-Entity', () => {
|
|||||||
|
|
||||||
it('should throw an error if numTokens is not included', () => {
|
it('should throw an error if numTokens is not included', () => {
|
||||||
try {
|
try {
|
||||||
const data = {
|
const orderData = {
|
||||||
messageType: 1,
|
data: {
|
||||||
messageClass: 1,
|
messageType: 1,
|
||||||
tokenId: 'fakeId',
|
messageClass: 1,
|
||||||
buyOrSell: 'buy',
|
tokenId: 'fakeId',
|
||||||
rateInSats: 1000,
|
buyOrSell: 'buy',
|
||||||
minSatsToExchange: 350
|
rateInSats: 1000,
|
||||||
|
minSatsToExchange: 350
|
||||||
|
}
|
||||||
}
|
}
|
||||||
uut.validate(data)
|
uut.validate(orderData)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.log(err)
|
// console.log(err)
|
||||||
assert.include(err.message, "Property 'numTokens' must be a number.")
|
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
|
const assert = require('chai').assert
|
||||||
@@ -27,17 +27,14 @@ describe('#Order-Entity', () => {
|
|||||||
uut.validate()
|
uut.validate()
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.log(err)
|
// console.log(err)
|
||||||
assert.include(
|
assert.include(err.message, 'Cannot destructure property')
|
||||||
err.message,
|
|
||||||
'Input to order.validate() must be an object with a data property.'
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should throw an error if messageType is not included', () => {
|
it('should throw an error if messageType is not included', () => {
|
||||||
try {
|
try {
|
||||||
const orderData = { data: {} }
|
const data = {}
|
||||||
uut.validate(orderData)
|
uut.validate(data)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.log(err)
|
// console.log(err)
|
||||||
assert.include(
|
assert.include(
|
||||||
@@ -49,8 +46,8 @@ describe('#Order-Entity', () => {
|
|||||||
|
|
||||||
it('should throw an error if messageClass is not included', () => {
|
it('should throw an error if messageClass is not included', () => {
|
||||||
try {
|
try {
|
||||||
const orderData = { data: { messageType: 1 } }
|
const data = { messageType: 1 }
|
||||||
uut.validate(orderData)
|
uut.validate(data)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.log(err)
|
// console.log(err)
|
||||||
assert.include(
|
assert.include(
|
||||||
@@ -62,8 +59,8 @@ describe('#Order-Entity', () => {
|
|||||||
|
|
||||||
it('should throw an error if tokenId is not included', () => {
|
it('should throw an error if tokenId is not included', () => {
|
||||||
try {
|
try {
|
||||||
const orderData = { data: { messageType: 1, messageClass: 1 } }
|
const data = { messageType: 1, messageClass: 1 }
|
||||||
uut.validate(orderData)
|
uut.validate(data)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.log(err)
|
// console.log(err)
|
||||||
assert.include(err.message, "Property 'tokenId' must be a string.")
|
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', () => {
|
it('should throw an error if buyOrSell is not included', () => {
|
||||||
try {
|
try {
|
||||||
const orderData = {
|
const data = { messageType: 1, messageClass: 1, tokenId: 'fakeId' }
|
||||||
data: { messageType: 1, messageClass: 1, tokenId: 'fakeId' }
|
uut.validate(data)
|
||||||
}
|
|
||||||
|
|
||||||
uut.validate(orderData)
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.log(err)
|
// console.log(err)
|
||||||
assert.include(err.message, "Property 'buyOrSell' must be a string.")
|
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', () => {
|
it('should throw an error if rateInSats is not included', () => {
|
||||||
try {
|
try {
|
||||||
const orderData = {
|
const data = {
|
||||||
data: {
|
messageType: 1,
|
||||||
messageType: 1,
|
messageClass: 1,
|
||||||
messageClass: 1,
|
tokenId: 'fakeId',
|
||||||
tokenId: 'fakeId',
|
buyOrSell: 'buy'
|
||||||
buyOrSell: 'buy'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
uut.validate(data)
|
||||||
uut.validate(orderData)
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.log(err)
|
// console.log(err)
|
||||||
assert.include(
|
assert.include(
|
||||||
@@ -106,17 +97,14 @@ describe('#Order-Entity', () => {
|
|||||||
|
|
||||||
it('should throw an error if minSatsToExchange is not included', () => {
|
it('should throw an error if minSatsToExchange is not included', () => {
|
||||||
try {
|
try {
|
||||||
const orderData = {
|
const data = {
|
||||||
data: {
|
messageType: 1,
|
||||||
messageType: 1,
|
messageClass: 1,
|
||||||
messageClass: 1,
|
tokenId: 'fakeId',
|
||||||
tokenId: 'fakeId',
|
buyOrSell: 'buy',
|
||||||
buyOrSell: 'buy',
|
rateInSats: 1000
|
||||||
rateInSats: 1000
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
uut.validate(data)
|
||||||
uut.validate(orderData)
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.log(err)
|
// console.log(err)
|
||||||
assert.include(
|
assert.include(
|
||||||
@@ -128,105 +116,19 @@ describe('#Order-Entity', () => {
|
|||||||
|
|
||||||
it('should throw an error if numTokens is not included', () => {
|
it('should throw an error if numTokens is not included', () => {
|
||||||
try {
|
try {
|
||||||
const orderData = {
|
const data = {
|
||||||
data: {
|
messageType: 1,
|
||||||
messageType: 1,
|
messageClass: 1,
|
||||||
messageClass: 1,
|
tokenId: 'fakeId',
|
||||||
tokenId: 'fakeId',
|
buyOrSell: 'buy',
|
||||||
buyOrSell: 'buy',
|
rateInSats: 1000,
|
||||||
rateInSats: 1000,
|
minSatsToExchange: 350
|
||||||
minSatsToExchange: 350
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
uut.validate(orderData)
|
uut.validate(data)
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// console.log(err)
|
// console.log(err)
|
||||||
assert.include(err.message, "Property 'numTokens' must be a number.")
|
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', () => {
|
describe('#createOffer', () => {
|
||||||
it('should return true if wallet has enough funds for a sell order', async () => {
|
it('should ignore an offer if utxo has been spent', async () => {
|
||||||
const offerEntity = {
|
const offerObj = {
|
||||||
lokadId: 'SWP',
|
appId: 'swapTest555',
|
||||||
messageType: 1,
|
data: {
|
||||||
messageClass: 1,
|
messageType: 1,
|
||||||
tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
messageClass: 1,
|
||||||
buyOrSell: 'sell',
|
tokenId:
|
||||||
rateInSats: 1000,
|
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
|
||||||
minSatsToExchange: 0,
|
buyOrSell: 'sell',
|
||||||
numTokens: 1
|
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 = 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)
|
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', () => {
|
describe('#ensureFunds', () => {
|
||||||
it('should ignore an offer if utxo has been spent', async () => {
|
it('should return true if wallet has enough funds for a sell order', async () => {
|
||||||
const orderObj = {
|
const orderEntity = {
|
||||||
appId: 'swapTest555',
|
lokadId: 'SWP',
|
||||||
data: {
|
messageType: 1,
|
||||||
messageType: 1,
|
messageClass: 1,
|
||||||
messageClass: 1,
|
tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
||||||
tokenId:
|
buyOrSell: 'sell',
|
||||||
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
|
rateInSats: 1000,
|
||||||
buyOrSell: 'sell',
|
minSatsToExchange: 0,
|
||||||
rateInSats: 1000,
|
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'
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mock dependencies
|
const result = await uut.ensureFunds(orderEntity)
|
||||||
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)
|
|
||||||
|
|
||||||
assert.equal(result, true)
|
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