fix(order): Refactored createOrder() to use Nostr

This commit is contained in:
Daniel Gonzalez
2024-11-28 12:37:47 -04:00
parent ed6d2af23c
commit af8a7c3ff3
6 changed files with 229 additions and 27 deletions
+2 -1
View File
@@ -37,7 +37,8 @@ const Order = new mongoose.Schema({
// SWaP Protocol Properties
lokadId: { type: String },
messageType: { type: Number },
messageClass: { type: Number }
messageClass: { type: Number },
nostrEventId: { type: String } // Nostr Event Id.
})
export default mongoose.model('order', Order)
+2 -2
View File
@@ -69,10 +69,10 @@ class OrderRESTControllerLib {
try {
// console.log('body: ', ctx.request.body)
const p2wdbHash = ctx.request.body.p2wdbHash
const nostrEventId = ctx.request.body.nostrEventId
// console.log('p2wdbHash: ', p2wdbHash)
const txid = await _this.useCases.order.deleteOrder(p2wdbHash)
const txid = await _this.useCases.order.deleteOrder(nostrEventId)
ctx.body = { txid }
} catch (err) {
+1 -1
View File
@@ -454,7 +454,7 @@ class OfferUseCases {
const orderHash = p2wdbData.data.offerHash
let orderData = {}
try {
orderData = await this.orderUseCase.findOrderByHash(orderHash)
orderData = await this.orderUseCase.findOrderByEvent(orderHash)
console.log(`orderData: ${JSON.stringify(orderData, null, 2)}`)
} catch (err) {
console.log('Order matching this Counter Offer is not managed by this instance of bch-dex. Exiting.')
+19 -20
View File
@@ -70,7 +70,7 @@ class OrderLib {
}
// const utxoInfo = await this.adapters.wallet.moveTokens(moveObj)
const utxoInfo = await this.retryQueue.addToQueue(this.adapters.wallet.moveTokens, moveObj)
console.log('utxoInfo: ', utxoInfo)
// console.log('utxoInfo: ', utxoInfo)
// Update the UTXO store for the wallet.
await this.adapters.wallet.bchWallet.bchjs.Util.sleep(3000)
@@ -86,24 +86,26 @@ class OrderLib {
orderEntity.dataType = 'offer'
// Add order to P2WDB.
const p2wdbObj = {
const postObj = {
wif: this.adapters.wallet.bchWallet.walletInfo.privateKey,
data: orderEntity,
appId: this.config.p2wdbAppId
}
// const hash = await this.adapters.p2wdb.write(p2wdbObj)
const hash = await this.retryQueue.addToQueue(this.adapters.p2wdb.write, p2wdbObj)
const postMsg = JSON.stringify(postObj)
const eventId = await this.adapters.nostr.post(postMsg)
// console.log('hash: ', hash)
// Create a MongoDB model to hold the Order
orderEntity.hdIndex = utxoInfo.hdIndex
orderEntity.p2wdbHash = hash
orderEntity.nostrEventId = eventId
console.log(`creating new order model: ${JSON.stringify(orderEntity, null, 2)}`)
const order = new this.OrderModel(orderEntity)
await order.save()
return hash
return eventId
} catch (err) {
// console.log("Error in use-cases/entry.js/createEntry()", err.message)
wlogger.error('Error in use-cases/order.js/createOrder())')
@@ -130,19 +132,17 @@ class OrderLib {
// Get UTXOs.
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 (orderEntity.buyOrSell.includes('sell')) {
// Sell Order
// Combine Fungible and NFT token UTXOs.
let tokenUtxos = utxos.slpUtxos.type1.tokens.concat(utxos.slpUtxos.nft.tokens)
// Get token UTXOs that match the token in the order.
tokenUtxos = tokenUtxos.filter(
x => x.tokenId === orderEntity.tokenId
)
console.log('tokenUtxos: ', tokenUtxos)
// Get the total amount of tokens in the wallet that match the token
// in the order.
@@ -171,14 +171,14 @@ class OrderLib {
}
}
// Retrieve an Order model from the database. Find it by its P2WDB CID.
async findOrderByHash (p2wdbHash) {
// Retrieve an Order model from the database. Find it by its event Id.
async findOrderByEvent (nostrEventId) {
try {
if (typeof p2wdbHash !== 'string' || !p2wdbHash) {
throw new Error('p2wdbHash must be a string')
if (typeof nostrEventId !== 'string' || !nostrEventId) {
throw new Error('nostrEventId must be a string')
}
const order = await this.OrderModel.findOne({ p2wdbHash })
const order = await this.OrderModel.findOne({ nostrEventId })
if (!order) {
throw new Error('order not found')
@@ -189,7 +189,7 @@ class OrderLib {
return orderObject
} catch (err) {
console.error('Error in findOrder()')
console.error('Error in findOrderByEvent()')
throw err
}
}
@@ -204,7 +204,7 @@ class OrderLib {
// Get all Orders in the database.
const orders = await this.OrderModel.find({})
// console.log('orders: ', orders)
console.log('orders: ', orders)
// Loop through each Order and ensure the UTXO is still valid.
for (let i = 0; i < orders.length; i++) {
@@ -256,6 +256,7 @@ class OrderLib {
async listOrders (page = 0) {
try {
const data = await this.OrderModel.find({})
// Sort entries so newest entries show first.
.sort('-timestamp')
// Skip to the start of the selected page.
@@ -272,12 +273,10 @@ class OrderLib {
// Delete an Order model by sending the token back to the root address. This
// will allow the garbage collectors to delete the Order and the Offer.
async deleteOrder (p2wdbHash) {
async deleteOrder (nostrEventId) {
try {
console.log('p2wdbHash: ', p2wdbHash)
// Find the order by the given hash.
const order = await this.findOrderByHash(p2wdbHash)
const order = await this.findOrderByEvent(nostrEventId)
console.log('order: ', order)
// Reclaim the tokens
+8 -2
View File
@@ -150,7 +150,8 @@ const wallet = {
return { cashAddress: 'fakeAddr', wif: 'fakeWif', hdIndex: 1 }
},
bchWallet: new MockBchWallet(),
moveTokens: async () => {}
moveTokens: async () => {},
reclaimTokens: async ()=>{}
}
const p2wdb = {
@@ -159,4 +160,9 @@ const p2wdb = {
checkForSufficientFunds: async () => true
}
export default { ipfs, localdb, bch, wallet, p2wdb, bchjs}
const nostr = {
post: async () => {return true },
read: async () => { return true }
}
export default { ipfs, localdb, bch, wallet, p2wdb, bchjs, nostr}
+197 -1
View File
@@ -62,6 +62,69 @@ describe('#order-use-case', () => {
assert.equal(result, true)
})
it('should handle insufficient funds', async () => {
try {
sandbox.stub(uut.adapters.p2wdb, 'checkForSufficientFunds').resolves(false)
const orderEntity = {
lokadId: 'SWP',
messageType: 1,
messageClass: 1,
tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
buyOrSell: 'sell',
rateInSats: 1000,
minSatsToExchange: 0,
numTokens: 1
}
await uut.ensureFunds(orderEntity)
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'App wallet does not have funds for writing to the P2WDB')
}
})
it('should handle insufficient tokens to satisfy the order', async () => {
try {
const orderEntity = {
lokadId: 'SWP',
messageType: 1,
messageClass: 1,
tokenId: '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
buyOrSell: 'sell',
rateInSats: 1000,
minSatsToExchange: 0,
numTokens: 1000
}
await uut.ensureFunds(orderEntity)
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'App wallet does not have enough tokens to satisfy the SELL order.')
}
})
it('should handle BUY order', async () => {
try {
const orderEntity = {
lokadId: 'SWP',
messageType: 1,
messageClass: 1,
tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
buyOrSell: 'buy',
rateInSats: 1000,
minSatsToExchange: 0,
numTokens: 1
}
await uut.ensureFunds(orderEntity)
assert.fail('Unexpected code path')
} catch (err) {
assert.include(err.message, 'Buy orders are not supported yet')
}
})
})
// describe('#moveTokens', () => {
@@ -113,7 +176,7 @@ describe('#order-use-case', () => {
sandbox.stub(uut.adapters.wallet.bchWallet.bchjs.Util, 'sleep').resolves()
sandbox.stub(uut.adapters.wallet, 'moveTokens').resolves({ txid: 'fakeTxid', vout: 0, hdIndex: 1 })
sandbox.stub(uut.adapters.wallet.bchWallet, 'initialize').resolves()
sandbox.stub(uut.adapters.p2wdb, 'write').resolves('fakeHash')
sandbox.stub(uut.adapters.nostr, 'post').resolves('fakeEvenetId')
const result = await uut.createOrder(entryObj)
console.log('result: ', result)
@@ -137,4 +200,137 @@ describe('#order-use-case', () => {
assert.equal(result, false)
})
})
describe('#findOrderByEvent', () => {
it('should throw an error if hash is not provided', async () => {
try {
await uut.findOrderByEvent()
} catch (err) {
assert.include(err.message, 'nostrEventId must be a string')
}
})
it('should throw an error if order is not found!', async () => {
try {
sandbox.stub(uut.OrderModel, 'findOne').resolves(null)
await uut.findOrderByEvent('hash')
} catch (err) {
assert.include(err.message, 'order not found')
}
})
it('should return order by hash', async () => {
sandbox.stub(uut.OrderModel, 'findOne').resolves({ toObject: () => { return { hash: 'hash' } } })
const result = await uut.findOrderByEvent('hash')
assert.isObject(result)
})
})
describe('#removeStaleOrders', () => {
it('should remove stale orders', async () => {
const spy = sinon.spy() // Spy the function
// Create 2 orders mock .
const ordersMock = new Array(2)
ordersMock.fill({ remove: () => { spy.call() } })
sandbox.stub(uut.OrderModel, 'find').resolves(ordersMock)
sandbox.stub(uut.retryQueue, 'addToQueue').resolves(false)
await uut.removeStaleOrders()
assert.equal(spy.callCount, 2, 'Expected to be called twice.')
})
it('should handle wrong txid', async () => {
const spy = sinon.spy() // Spy the function
// Create 2 orders mock .
const ordersMock = new Array(2)
ordersMock.fill({ remove: () => { spy.call() } })
sandbox.stub(uut.OrderModel, 'find').resolves(ordersMock)
sandbox.stub(uut.retryQueue, 'addToQueue').throws(new Error('txid needs to be a proper transaction ID'))
await uut.removeStaleOrders()
assert.equal(spy.callCount, 2, 'Expected to be called twice.')
})
it('should skip order on axios error', async () => {
const spy = sinon.spy() // Spy the function
// Create 2 orders mock .
const ordersMock = new Array(2)
ordersMock.fill({ remove: () => { spy.call() } })
const axiosError = new Error()
axiosError.isAxiosError = true
sandbox.stub(uut.OrderModel, 'find').resolves(ordersMock)
sandbox.stub(uut.retryQueue, 'addToQueue').throws(axiosError)
await uut.removeStaleOrders()
assert.equal(spy.callCount, 0, 'Expected to no be called.')
})
it('should handle unknow error', async () => {
try {
// Create 2 orders mock .
const ordersMock = new Array(2)
ordersMock.fill({ remove: () => { } })
sandbox.stub(uut.OrderModel, 'find').resolves(ordersMock)
sandbox.stub(uut.retryQueue, 'addToQueue').throws(new Error('test error'))
await uut.removeStaleOrders()
assert.fail('unexpected code path')
} catch (error) {
assert.include(error.message, 'test error')
}
})
})
describe('#listOrders', () => {
it('should handle error', async () => {
try {
sandbox.stub(uut.OrderModel, 'find').throws(new Error('test error'))
await uut.listOrders()
assert.fail('unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should list orders', async () => {
const queryMock = {
sort () {
return this
},
skip () {
return this
},
limit () { return [] }
}
sandbox.stub(uut.OrderModel, 'find').returns(queryMock)
const result = await uut.listOrders()
assert.isArray(result)
})
})
describe('#deleteOrder', () => {
it('should handle error', async () => {
try {
sandbox.stub(uut, 'findOrderByEvent').throws(new Error('test error'))
await uut.deleteOrder()
assert.fail('unexpected code path')
} catch (err) {
assert.include(err.message, 'test error')
}
})
it('should delete orders', async () => {
sandbox.stub(uut, 'findOrderByEvent').resolves([])
sandbox.stub(uut.adapters.wallet, 'reclaimTokens').resolves('txid')
const result = await uut.deleteOrder()
assert.isString(result)
})
})
})