From af8a7c3ff3a6aad8661191de40dcfa60044fcc55 Mon Sep 17 00:00:00 2001 From: Daniel Gonzalez Date: Thu, 28 Nov 2024 12:37:47 -0400 Subject: [PATCH 1/2] fix(order): Refactored createOrder() to use Nostr --- src/adapters/localdb/models/order.js | 3 +- src/controllers/rest-api/order/controller.js | 4 +- src/use-cases/offer/index.js | 2 +- src/use-cases/order.js | 39 ++-- test/unit/mocks/adapters/index.js | 10 +- test/unit/use-cases/order.use-case.unit.js | 198 ++++++++++++++++++- 6 files changed, 229 insertions(+), 27 deletions(-) diff --git a/src/adapters/localdb/models/order.js b/src/adapters/localdb/models/order.js index c1d2af8..8e72d8b 100644 --- a/src/adapters/localdb/models/order.js +++ b/src/adapters/localdb/models/order.js @@ -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) diff --git a/src/controllers/rest-api/order/controller.js b/src/controllers/rest-api/order/controller.js index fd4ac1a..555a802 100644 --- a/src/controllers/rest-api/order/controller.js +++ b/src/controllers/rest-api/order/controller.js @@ -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) { diff --git a/src/use-cases/offer/index.js b/src/use-cases/offer/index.js index 7313bb5..61a23a6 100644 --- a/src/use-cases/offer/index.js +++ b/src/use-cases/offer/index.js @@ -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.') diff --git a/src/use-cases/order.js b/src/use-cases/order.js index dbf36b2..822cbfa 100644 --- a/src/use-cases/order.js +++ b/src/use-cases/order.js @@ -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 diff --git a/test/unit/mocks/adapters/index.js b/test/unit/mocks/adapters/index.js index 5dbbdd2..fb4f9da 100644 --- a/test/unit/mocks/adapters/index.js +++ b/test/unit/mocks/adapters/index.js @@ -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} diff --git a/test/unit/use-cases/order.use-case.unit.js b/test/unit/use-cases/order.use-case.unit.js index 1669d4a..96fbdaa 100644 --- a/test/unit/use-cases/order.use-case.unit.js +++ b/test/unit/use-cases/order.use-case.unit.js @@ -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) + }) + }) }) From df146382cb37461a9294f3b755df82f5fe057955 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Fri, 29 Nov 2024 04:00:10 -0800 Subject: [PATCH 2/2] Reducing info posted to Nostr on Order --- src/use-cases/order.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/use-cases/order.js b/src/use-cases/order.js index 822cbfa..505e402 100644 --- a/src/use-cases/order.js +++ b/src/use-cases/order.js @@ -85,15 +85,12 @@ class OrderLib { // Add P2WDB specific flag for signaling that this is a new offer. orderEntity.dataType = 'offer' - // Add order to P2WDB. + // Post the new Order information to Nostr under the topic set in the + // config file. const postObj = { - wif: this.adapters.wallet.bchWallet.walletInfo.privateKey, - data: orderEntity, - appId: this.config.p2wdbAppId + data: orderEntity } - const postMsg = JSON.stringify(postObj) - const eventId = await this.adapters.nostr.post(postMsg) // console.log('hash: ', hash)