From 5fc26dcc7cb605ca155ceca72ea87a6361f826ec Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Wed, 9 Mar 2022 14:25:37 -0800 Subject: [PATCH 1/8] fix(order): Adding 'orderStatus' to Order model --- src/adapters/localdb/models/order.js | 3 ++- src/entities/order.js | 22 ++++++++++------------ src/use-cases/order/index.js | 3 +++ 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/src/adapters/localdb/models/order.js b/src/adapters/localdb/models/order.js index e8119b1..cf85686 100644 --- a/src/adapters/localdb/models/order.js +++ b/src/adapters/localdb/models/order.js @@ -17,7 +17,8 @@ const Order = new mongoose.Schema({ timestamp: { type: String }, localTimestamp: { type: String }, p2wdbTxid: { type: String }, - p2wdbHash: { type: String } + p2wdbHash: { type: String }, + orderStatus: { type: String } }) module.exports = mongoose.model('order', Order) diff --git a/src/entities/order.js b/src/entities/order.js index 040af00..3bb1987 100644 --- a/src/entities/order.js +++ b/src/entities/order.js @@ -4,6 +4,10 @@ It's destroyed when the UTXO described in the Signal has been detected as spent. */ class OrderEntity { + constructor () { + this.orderStatus = ['posted', 'taken', 'completed'] + } + validate (orderData = {}) { // Throw an error if input object does not have a data property if (!orderData.data) { @@ -12,17 +16,7 @@ class OrderEntity { ) } - const { - messageType, - messageClass, - tokenId, - buyOrSell, - rateInSats, - minSatsToExchange, - numTokens, - utxoTxid, - utxoVout - } = orderData.data + const {messageType, messageClass, tokenId, buyOrSell, rateInSats, minSatsToExchange, numTokens, utxoTxid, utxoVout, orderStatus} = orderData.data // Input Validation if (!messageType || typeof messageType !== 'number') { @@ -52,6 +46,9 @@ class OrderEntity { 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 = { messageType, @@ -66,7 +63,8 @@ class OrderEntity { timestamp: orderData.timestamp, localTimestamp: orderData.localTimeStamp, txid: orderData.txid, - p2wdbHash: orderData.hash + p2wdbHash: orderData.hash, + orderStatus: orderStatus || this.orderStatus[0] } return validatedOrderData diff --git a/src/use-cases/order/index.js b/src/use-cases/order/index.js index 9ee758c..cc5331c 100644 --- a/src/use-cases/order/index.js +++ b/src/use-cases/order/index.js @@ -43,6 +43,9 @@ class OrderUseCases { console.log('utxoStatus: ', utxoStatus) if (utxoStatus === null) return false + // A new order gets a status of 'posted' + orderObj.data.orderStatus = 'posted' + const orderEntity = this.orderEntity.validate(orderObj) console.log('orderEntity: ', orderEntity) From e68514f54225dabd67f9e9830c7604e97f40670a Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Wed, 9 Mar 2022 14:25:51 -0800 Subject: [PATCH 2/8] linting --- src/entities/order.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/entities/order.js b/src/entities/order.js index 3bb1987..803bcd5 100644 --- a/src/entities/order.js +++ b/src/entities/order.js @@ -16,7 +16,7 @@ class OrderEntity { ) } - const {messageType, messageClass, tokenId, buyOrSell, rateInSats, minSatsToExchange, numTokens, utxoTxid, utxoVout, orderStatus} = orderData.data + const { messageType, messageClass, tokenId, buyOrSell, rateInSats, minSatsToExchange, numTokens, utxoTxid, utxoVout, orderStatus } = orderData.data // Input Validation if (!messageType || typeof messageType !== 'number') { From bf7181097415b570d4e1dd92611aff7fe5908b45 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Thu, 10 Mar 2022 10:33:34 -0800 Subject: [PATCH 3/8] Adding wallet sweep script for cleaning up test wallets --- package-lock.json | 22 +++++++++++ package.json | 1 + util/wallet/sweep-funds.js | 77 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 util/wallet/sweep-funds.js diff --git a/package-lock.json b/package-lock.json index 253d420..33aa0c0 100644 --- a/package-lock.json +++ b/package-lock.json @@ -45,6 +45,7 @@ }, "devDependencies": { "apidoc": "0.26.0", + "bch-token-sweep": "1.5.16", "chai": "4.3.0", "coveralls": "2.11.4", "eslint": "7.19.0", @@ -2972,6 +2973,17 @@ "minimal-slp-wallet": "4.4.1" } }, + "node_modules/bch-token-sweep": { + "version": "1.5.16", + "resolved": "http://94.130.170.209:4873/bch-token-sweep/-/bch-token-sweep-1.5.16.tgz", + "integrity": "sha512-9Fsb0qXUZ62OUDdH/tIF8QZDs12vWduoGDtU9txpSEF6GEyK0NOHfu8YYx5henoHz7SGS/fMUWDCibmedSpsmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "bch-donation": "1.1.1", + "bignumber.js": "9.0.0" + } + }, "node_modules/bchaddrjs-slp": { "version": "0.2.5", "resolved": "http://94.130.170.209:4873/bchaddrjs-slp/-/bchaddrjs-slp-0.2.5.tgz", @@ -24320,6 +24332,16 @@ "version": "2.1.4", "requires": {} }, + "bch-token-sweep": { + "version": "1.5.16", + "resolved": "http://94.130.170.209:4873/bch-token-sweep/-/bch-token-sweep-1.5.16.tgz", + "integrity": "sha512-9Fsb0qXUZ62OUDdH/tIF8QZDs12vWduoGDtU9txpSEF6GEyK0NOHfu8YYx5henoHz7SGS/fMUWDCibmedSpsmA==", + "dev": true, + "requires": { + "bch-donation": "1.1.1", + "bignumber.js": "9.0.0" + } + }, "bchaddrjs-slp": { "version": "0.2.5", "resolved": "http://94.130.170.209:4873/bchaddrjs-slp/-/bchaddrjs-slp-0.2.5.tgz", diff --git a/package.json b/package.json index bacdcd5..6dfb5fd 100644 --- a/package.json +++ b/package.json @@ -63,6 +63,7 @@ }, "devDependencies": { "apidoc": "0.26.0", + "bch-token-sweep": "1.5.16", "chai": "4.3.0", "coveralls": "2.11.4", "eslint": "7.19.0", diff --git a/util/wallet/sweep-funds.js b/util/wallet/sweep-funds.js new file mode 100644 index 0000000..ea27250 --- /dev/null +++ b/util/wallet/sweep-funds.js @@ -0,0 +1,77 @@ +/* + This script will travers the HD wallet and sweep funds and tokens back + into the root address (index 0). That root address needs to have funds + to pay for the transactions. + + The root address will make a final transaction to consolidate all tokens. +*/ + +// Public npm libraries +const BCHJS = require('@psf/bch-js') +const BchTokenSweep = require('bch-token-sweep/index') + +// Local libraries +const WalletAdapter = require('../../src/adapters/wallet') + +async function sweepFunds () { + try { + // Open the wallet files. + const wallet = new WalletAdapter() + const walletInfo = await wallet.openWallet() + console.log('walletInfo: ', walletInfo) + + const rootAddr = walletInfo.cashAddress + const rootWif = walletInfo.privateKey + console.log(`Sweeping all funds into root address ${rootAddr}...`) + + // Generate an HD tree + const bchjs = new BCHJS() + const rootSeed = await bchjs.Mnemonic.toSeed(walletInfo.mnemonic) + const masterHDNode = bchjs.HDNode.fromSeed(rootSeed) + + let emptyAddrCnt = 0 + let hdIndex = 1 + + do { + // Generate a keypair from the HD wallet. + const childNode = masterHDNode.derivePath(`m/44'/245'/0'/0/${hdIndex}`) + // const cashAddress = bchjs.HDNode.toCashAddress(childNode) + const wifToSweep = bchjs.HDNode.toWIF(childNode) + + try { + // Sweep tokens from address + const sweeper = new BchTokenSweep( + wifToSweep, + rootWif, + bchjs, + 550, + rootAddr + ) + await sweeper.populateObjectFromNetwork() + + const hex = await sweeper.sweepTo(rootAddr) + // console.log(`hex: ${hex}`) + + const txid = await sweeper.blockchain.broadcast(hex) + + // console.log('Transaction ID', txid) + console.log(`Swept HD index ${hdIndex}. TXID: ${txid}`) + + emptyAddrCnt = 0 + + // Wait between loop iterations. + await bchjs.Util.sleep(3000) + } catch (err) { + console.log(`error message: ${err.message}`) + emptyAddrCnt++ + } + + hdIndex++ + } while (emptyAddrCnt < 5) + + console.log('5 empty addresses detected. Exiting.') + } catch (err) { + console.error('Error in sweepFunds(): ', err) + } +} +sweepFunds() From a6b467757196fcc3628b5ee5c5e3b02da0ef20c7 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Thu, 10 Mar 2022 13:21:25 -0800 Subject: [PATCH 4/8] Working on take-offer logic --- src/adapters/wallet.js | 70 +++--------- src/controllers/rest-api/order/controller.js | 21 ++++ src/controllers/rest-api/order/index.js | 1 + src/use-cases/offer.js | 2 +- src/use-cases/order/index.js | 106 ++++++++++++++++++ .../{create-offer.js => 01-create-order.js} | 0 test/e2e/manual/02-take-order.js | 25 +++++ 7 files changed, 167 insertions(+), 58 deletions(-) rename test/e2e/manual/{create-offer.js => 01-create-order.js} (100%) create mode 100644 test/e2e/manual/02-take-order.js diff --git a/src/adapters/wallet.js b/src/adapters/wallet.js index 842bacc..ff226b5 100644 --- a/src/adapters/wallet.js +++ b/src/adapters/wallet.js @@ -178,63 +178,19 @@ class WalletAdapter { } } -// Burn enough PSF to generate a valide proof-of-burn for writing to the P2WDB. -// async burnPsf () { -// try { -// // TODO: Throw error if this.bchWallet has not been instantiated. -// -// // console.log('walletData: ', walletData) -// // console.log( -// // `walletData.utxos.utxoStore.slpUtxos: ${JSON.stringify( -// // walletData.utxos.utxoStore.slpUtxos, -// // null, -// // 2, -// // )}`, -// // ) -// -// // Get token UTXOs held by the wallet. -// const tokenUtxos = this.bchWallet.utxos.utxoStore.slpUtxos.type1.tokens -// // console.log(`tokenUtxos: ${JSON.stringify(tokenUtxos, null, 2)}`) -// -// // Find a token UTXO that contains PSF with a quantity higher than needed -// // to generate a proof-of-burn. -// let tokenUtxo = {} -// for (let i = 0; i < tokenUtxos.length; i++) { -// const thisUtxo = tokenUtxos[i] -// -// // If token ID matches. -// if (thisUtxo.tokenId === P2WDB_TOKEN_ID) { -// if (parseFloat(thisUtxo.qtyStr) >= PROOF_OF_BURN_QTY) { -// tokenUtxo = thisUtxo -// break -// } -// } -// } -// -// if (tokenUtxo.tokenId !== P2WDB_TOKEN_ID) { -// throw new Error( -// `Token UTXO of with ID of ${P2WDB_TOKEN_ID} and quantity greater than ${PROOF_OF_BURN_QTY} could not be found in wallet.` -// ) -// } -// // console.log(`tokenUtxo: ${JSON.stringify(tokenUtxo, null, 2)}`) -// -// const result = await this.bchWallet.burnTokens( -// PROOF_OF_BURN_QTY, -// P2WDB_TOKEN_ID -// ) -// // console.log('walletData.burnTokens() result: ', result) -// -// return result -// -// // return { -// // success: true, -// // txid: 'fakeTxid', -// // } -// } catch (err) { -// console.error('Error in burnPsf(): ', err) -// throw err -// } -// } + // Generate a partial transcation to *take* a 'sell' order. + async generatePartialTx (orderInfo) { + // try { + // // const bchjs = this.bchWallet.bchjs + // + // return true + // } catch (err) { + // console.error('Error in wallet.js/generatePartialTx()') + // throw err + // } + + return true + } } module.exports = WalletAdapter diff --git a/src/controllers/rest-api/order/controller.js b/src/controllers/rest-api/order/controller.js index cd09aee..86f50d7 100644 --- a/src/controllers/rest-api/order/controller.js +++ b/src/controllers/rest-api/order/controller.js @@ -61,6 +61,27 @@ class OrderRESTControllerLib { } } + // Currently only supports 'sell' orders, and will only buy the 'numTokens' + // listed in the order. + async takeOrder (ctx) { + try { + console.log('body: ', ctx.request.body) + + const orderCid = ctx.request.body.orderCid + + // Find the Order. + // const orderEntity = await _this.useCases.order.findOrder(orderId) + + // 'Take' the Order. + const hash = await _this.useCases.order.takeOrder(orderCid) + + ctx.body = { hash } + } catch (err) { + console.log('Error in takeOrder REST API handler.') + _this.handleError(ctx, err) + } + } + // DRY error handler handleError (ctx, err) { console.log('err', err.message) diff --git a/src/controllers/rest-api/order/index.js b/src/controllers/rest-api/order/index.js index 8e6712a..8a80625 100644 --- a/src/controllers/rest-api/order/index.js +++ b/src/controllers/rest-api/order/index.js @@ -51,6 +51,7 @@ class OrderRouter { // Define the routes and attach the controller. this.router.post('/', _this.orderRESTController.createOrder) this.router.get('/list', _this.orderRESTController.listOrders) + this.router.post('/take', _this.orderRESTController.takeOrder) // Attach the Controller routes to the Koa app. app.use(_this.router.routes()) diff --git a/src/use-cases/offer.js b/src/use-cases/offer.js index 41419a6..21a75a9 100644 --- a/src/use-cases/offer.js +++ b/src/use-cases/offer.js @@ -90,7 +90,7 @@ class OfferLib { const utxoInfo = { txid, - vout: 0, + vout: 1, hdIndex: keyPair.hdIndex } diff --git a/src/use-cases/order/index.js b/src/use-cases/order/index.js index cc5331c..d8a1e40 100644 --- a/src/use-cases/order/index.js +++ b/src/use-cases/order/index.js @@ -68,6 +68,112 @@ class OrderUseCases { throw error } } + + // Generate phase 2 of 3 - take the other side of an Order. + // Based on this example: + // https://github.com/Permissionless-Software-Foundation/bch-js-examples/blob/master/bch/applications/collaborate/sell-slp/e2e-exchange/step2-purchase-tx.js + async takeOrder (orderCid) { + try { + console.log('orderCid: ', orderCid) + + // Get the Order information + const orderInfo = await this.findOrderByHash(orderCid) + console.log(`orderInfo: ${JSON.stringify(orderInfo, null, 2)}`) + + // Ensure the order is in a 'posted' state and not already 'taken' + if (orderInfo.orderStatus && orderInfo.orderStatus !== 'posted') { + throw new Error('order already taken') + } + + // Verify that UTXO for sale is unspent. Abort if it's been spent. + const txid = orderInfo.utxoTxid + const vout = orderInfo.utxoVout + const utxoStatus = await this.adapters.bchjs.Blockchain.getTxOut( + txid, + vout + ) + console.log('utxoStatus: ', utxoStatus) + if (utxoStatus === null) { + console.log(`utxo txid: ${txid}, vout: ${vout}`) + throw new Error('UTXO does not exist. Aborting.') + } + + // Ensure the app has enough funds to complete the trade. + await this.ensureFunds(orderInfo) + + // Get UTXOs. + const utxos = this.adapters.wallet.bchWallet.utxos.utxoStore + console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`) + + // Create a partially signed transaction. + // https://github.com/Permissionless-Software-Foundation/bch-js-examples/blob/master/bch/applications/collaborate/sell-slp/e2e-exchange/step2-purchase-tx.js#L59 + // const partialTxHex = await this.adapaters.wallet.generatePartialTx(orderInfo) + + // return partialTxHex + + return true + } catch (err) { + console.error('Error in use-cases/order/takeOrder()') + throw err + } + } + + // Ensure that the wallet has enough BCH and tokens to complete the requested + // trade. Will return true if it does. Will throw an error if it doesn't. + async ensureFunds (orderEntity) { + try { + // console.log('this.adapters.wallet: ', this.adapters.wallet.bchWallet) + // console.log(`walletInfo: ${JSON.stringify(this.adapters.wallet.bchWallet.walletInfo, null, 2)}`) + + // Ensure the app wallet has enough funds to write to the P2WDB. + const wif = this.adapters.wallet.bchWallet.walletInfo.privateKey + const canWriteToP2WDB = await this.adapters.p2wdb.checkForSufficientFunds(wif) + if (!canWriteToP2WDB) throw new Error('App wallet does not have funds for writing to the P2WDB.') + + if (orderEntity.buyOrSell.includes('sell')) { + // Sell Offer + + // Ensure the app wallet controlls enough BCH to pay for the tokens. + const satsNeeded = orderEntity.numTokens * parseInt(orderEntity.rateInSats) + const balance = await this.adapters.wallet.bchWallet.getBalance() + console.log(`wallet balance: ${balance}, sats needed: ${satsNeeded}`) + const SATS_MARGIN = 5000 + if (satsNeeded + SATS_MARGIN > balance) { throw new Error('App wallet does not control enough BCH to purchase the tokens.') } + + // + } else { + // Buy Offer + throw new Error('Buy orders are not supported yet.') + } + + return true + } catch (err) { + console.error('Error in ensureFunds()') + throw err + } + } + + async findOrderByHash (p2wdbHash) { + try { + if (typeof p2wdbHash !== 'string' || !p2wdbHash) { + throw new Error('p2wdbHash must be a string') + } + + const order = await this.OrderModel.findOne({ p2wdbHash }) + + if (!order) { + throw new Error('order not found') + } + + const orderObject = order.toObject() + // return this.orderEntity.validateFromModel(orderObject) + + return orderObject + } catch (err) { + console.error('Error in findOrder(): ', err) + throw err + } + } } module.exports = OrderUseCases diff --git a/test/e2e/manual/create-offer.js b/test/e2e/manual/01-create-order.js similarity index 100% rename from test/e2e/manual/create-offer.js rename to test/e2e/manual/01-create-order.js diff --git a/test/e2e/manual/02-take-order.js b/test/e2e/manual/02-take-order.js new file mode 100644 index 0000000..7b21377 --- /dev/null +++ b/test/e2e/manual/02-take-order.js @@ -0,0 +1,25 @@ +/* + Part 2 of 3: Take an order +*/ + +const axios = require('axios') + +const LOCALHOST = 'http://localhost:5700' + +async function start () { + try { + const options = { + method: 'post', + url: `${LOCALHOST}/order/take`, + data: { + orderCid: 'zdpuAkp98gTuivaNzGP31jTQi3ADXrFA6uANrceQcrQkTXy2j' + } + } + + const result = await axios(options) + console.log('result.data: ', result.data) + } catch (err) { + console.log(err) + } +} +start() From 87b9046af8545fcb99d4d23916ee744dd0fb271a Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Thu, 10 Mar 2022 15:50:13 -0800 Subject: [PATCH 5/8] fix(offer): Fixing bug. vout = 1 --- test/unit/use-cases/offer.use-case.unit.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/unit/use-cases/offer.use-case.unit.js b/test/unit/use-cases/offer.use-case.unit.js index ece4c65..3cb2258 100644 --- a/test/unit/use-cases/offer.use-case.unit.js +++ b/test/unit/use-cases/offer.use-case.unit.js @@ -90,7 +90,7 @@ describe('#offer-use-case', () => { assert.property(result, 'vout') assert.equal(result.txid, 'fakeTxid') - assert.equal(result.vout, 0) + assert.equal(result.vout, 1) }) }) From a7f8a520a2d852cb7d680fe4f2247dd5fc93716a Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Fri, 11 Mar 2022 06:35:56 -0800 Subject: [PATCH 6/8] Got /order/take to generate a partial TX --- package-lock.json | 338 ++++++++++++++++++++--------------- package.json | 6 +- src/adapters/wallet.js | 130 +++++++++++++- src/use-cases/order/index.js | 6 +- 4 files changed, 318 insertions(+), 162 deletions(-) diff --git a/package-lock.json b/package-lock.json index 33aa0c0..6007d75 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,13 +9,13 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@psf/bch-js": "5.3.2", + "@psf/bch-js": "../bch-js", "axios": "0.21.1", "bch-message-lib": "2.1.4", "bcryptjs": "2.4.3", "glob": "7.1.6", "ipfs": "0.60.0", - "ipfs-coord": "7.1.4", + "ipfs-coord": "7.1.6", "ipfs-http-client": "55.0.0", "jsonrpc-lite": "2.2.0", "jsonwebtoken": "8.5.1", @@ -33,7 +33,7 @@ "koa2-ratelimit": "0.9.1", "libp2p": "^0.36.2", "line-reader": "0.4.0", - "minimal-slp-wallet": "4.4.2", + "minimal-slp-wallet": "../minimal-slp-wallet", "mongoose": "5.13.13", "node-fetch": "npm:@achingbrain/node-fetch@2.6.7", "nodemailer": "6.4.17", @@ -67,7 +67,6 @@ "../bch-js": { "name": "@psf/bch-js", "version": "5.0.0", - "extraneous": true, "license": "MIT", "dependencies": { "@chris.troutner/bip32-utils": "1.0.5", @@ -120,7 +119,6 @@ }, "../minimal-slp-wallet": { "version": "4.0.0", - "extraneous": true, "license": "MIT", "dependencies": { "@psf/bch-js": "5.3.2", @@ -1605,62 +1603,8 @@ "license": "BSD-3-Clause" }, "node_modules/@psf/bch-js": { - "version": "5.3.2", - "resolved": "http://94.130.170.209:4873/@psf%2fbch-js/-/bch-js-5.3.2.tgz", - "integrity": "sha512-LzYekwuee+T8AEZ1MKBe5KnZKXzpRZ57SpXRL2S8qsHefokTIN+XnDPX7dsOFkN4VGGgyjYyZ33iN6xhOSHWJw==", - "license": "MIT", - "dependencies": { - "@chris.troutner/bip32-utils": "1.0.5", - "@psf/bip21": "2.0.1", - "@psf/bitcoincash-ops": "2.0.0", - "@psf/bitcoincashjs-lib": "4.0.2", - "@psf/coininfo": "4.0.0", - "@uppy/core": "1.10.4", - "@uppy/tus": "1.5.12", - "axios": "^0.21.4", - "bc-bip68": "1.0.5", - "bchaddrjs-slp": "0.2.5", - "bigi": "1.4.2", - "bignumber.js": "9.0.0", - "bip-schnorr": "0.3.0", - "bip38": "2.0.2", - "bip39": "3.0.2", - "bip66": "1.1.5", - "bitcoinjs-message": "2.0.0", - "bs58": "4.0.1", - "ecashaddrjs": "1.0.7", - "ini": "1.3.8", - "randombytes": "2.0.6", - "safe-buffer": "5.1.2", - "satoshi-bitcoin": "1.0.4", - "slp-mdm": "0.0.6", - "slp-parser": "0.0.4", - "wif": "2.0.6" - } - }, - "node_modules/@psf/bch-js/node_modules/axios": { - "version": "0.21.4", - "resolved": "http://94.130.170.209:4873/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.14.0" - } - }, - "node_modules/@psf/bch-js/node_modules/randombytes": { - "version": "2.0.6", - "resolved": "http://94.130.170.209:4873/randombytes/-/randombytes-2.0.6.tgz", - "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", - "license": "MIT", - "dependencies": { - "safe-buffer": "^5.1.0" - } - }, - "node_modules/@psf/bch-js/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "http://94.130.170.209:4873/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" + "resolved": "../bch-js", + "link": true }, "node_modules/@psf/bip21": { "version": "2.0.1", @@ -8423,9 +8367,10 @@ } }, "node_modules/ipfs-coord": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/ipfs-coord/-/ipfs-coord-7.1.4.tgz", - "integrity": "sha512-YsCdrQ30JW03uOLNrpZKqNmWr5zmxF9AyKeXSQA/OTPBoOOqM2nomEYhTPVJjGxee9Qa2dtxCQzw3JwsLDCe4Q==", + "version": "7.1.6", + "resolved": "http://94.130.170.209:4873/ipfs-coord/-/ipfs-coord-7.1.6.tgz", + "integrity": "sha512-wtdJ/PgRMv3r2pOZ1WIc7blTkk3YftMbXTNX9hN9Lu+IblUhNoWUyjq7E4i7J3zBZQVQs7J3TyckRdLCekmc9A==", + "license": "MIT", "dependencies": { "axios": "0.21.4", "bch-encrypt-lib": "2.0.0", @@ -13024,39 +12969,8 @@ } }, "node_modules/minimal-slp-wallet": { - "version": "4.4.2", - "resolved": "http://94.130.170.209:4873/minimal-slp-wallet/-/minimal-slp-wallet-4.4.2.tgz", - "integrity": "sha512-6JNRnH8mQQb/gmfwdd0KgJeHFJycK60EDmeEdjghvm1I2J0z2HVI3gZzrVqjBC/tBDYKtuDuvRtix7q2LqKqhw==", - "license": "MIT", - "dependencies": { - "@psf/bch-js": "5.3.2", - "apidoc": "0.25.0", - "bch-consumer": "1.2.0", - "bch-donation": "1.1.1", - "crypto-js": "4.0.0" - } - }, - "node_modules/minimal-slp-wallet/node_modules/apidoc": { - "version": "0.25.0", - "resolved": "http://94.130.170.209:4873/apidoc/-/apidoc-0.25.0.tgz", - "integrity": "sha512-5g9fp8OffXZOdBTzm4BBvV5Vw54s+NmKnGZIUKuH+gRTqqJuRJpcGN6sz6WnjJ+NcvXhB7rIRp6FhtJahazx2Q==", - "license": "MIT", - "dependencies": { - "apidoc-core": "^0.12.0", - "commander": "^2.20.0", - "fs-extra": "^9.0.1", - "handlebars": "^4.7.6", - "lodash": "^4.17.20", - "markdown-it": "^11.0.0", - "nodemon": "^2.0.4", - "winston": "^3.3.3" - }, - "bin": { - "apidoc": "bin/apidoc" - }, - "engines": { - "node": ">= 0.10.0" - } + "resolved": "../minimal-slp-wallet", + "link": true }, "node_modules/minimalistic-assert": { "version": "1.0.1", @@ -17935,6 +17849,49 @@ "minimal-slp-wallet": "4.4.2" } }, + "node_modules/p2wdb/node_modules/@psf/bch-js": { + "version": "5.3.2", + "resolved": "http://94.130.170.209:4873/@psf%2fbch-js/-/bch-js-5.3.2.tgz", + "integrity": "sha512-LzYekwuee+T8AEZ1MKBe5KnZKXzpRZ57SpXRL2S8qsHefokTIN+XnDPX7dsOFkN4VGGgyjYyZ33iN6xhOSHWJw==", + "license": "MIT", + "dependencies": { + "@chris.troutner/bip32-utils": "1.0.5", + "@psf/bip21": "2.0.1", + "@psf/bitcoincash-ops": "2.0.0", + "@psf/bitcoincashjs-lib": "4.0.2", + "@psf/coininfo": "4.0.0", + "@uppy/core": "1.10.4", + "@uppy/tus": "1.5.12", + "axios": "^0.21.4", + "bc-bip68": "1.0.5", + "bchaddrjs-slp": "0.2.5", + "bigi": "1.4.2", + "bignumber.js": "9.0.0", + "bip-schnorr": "0.3.0", + "bip38": "2.0.2", + "bip39": "3.0.2", + "bip66": "1.1.5", + "bitcoinjs-message": "2.0.0", + "bs58": "4.0.1", + "ecashaddrjs": "1.0.7", + "ini": "1.3.8", + "randombytes": "2.0.6", + "safe-buffer": "5.1.2", + "satoshi-bitcoin": "1.0.4", + "slp-mdm": "0.0.6", + "slp-parser": "0.0.4", + "wif": "2.0.6" + } + }, + "node_modules/p2wdb/node_modules/@psf/bch-js/node_modules/axios": { + "version": "0.21.4", + "resolved": "http://94.130.170.209:4873/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, "node_modules/p2wdb/node_modules/apidoc": { "version": "0.25.0", "resolved": "http://94.130.170.209:4873/apidoc/-/apidoc-0.25.0.tgz", @@ -17966,6 +17923,34 @@ "follow-redirects": "^1.14.4" } }, + "node_modules/p2wdb/node_modules/minimal-slp-wallet": { + "version": "4.4.2", + "resolved": "http://94.130.170.209:4873/minimal-slp-wallet/-/minimal-slp-wallet-4.4.2.tgz", + "integrity": "sha512-6JNRnH8mQQb/gmfwdd0KgJeHFJycK60EDmeEdjghvm1I2J0z2HVI3gZzrVqjBC/tBDYKtuDuvRtix7q2LqKqhw==", + "license": "MIT", + "dependencies": { + "@psf/bch-js": "5.3.2", + "apidoc": "0.25.0", + "bch-consumer": "1.2.0", + "bch-donation": "1.1.1", + "crypto-js": "4.0.0" + } + }, + "node_modules/p2wdb/node_modules/randombytes": { + "version": "2.0.6", + "resolved": "http://94.130.170.209:4873/randombytes/-/randombytes-2.0.6.tgz", + "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/p2wdb/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "http://94.130.170.209:4873/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, "node_modules/package-hash": { "version": "4.0.0", "dev": true, @@ -23333,9 +23318,7 @@ "version": "1.1.0" }, "@psf/bch-js": { - "version": "5.3.2", - "resolved": "http://94.130.170.209:4873/@psf%2fbch-js/-/bch-js-5.3.2.tgz", - "integrity": "sha512-LzYekwuee+T8AEZ1MKBe5KnZKXzpRZ57SpXRL2S8qsHefokTIN+XnDPX7dsOFkN4VGGgyjYyZ33iN6xhOSHWJw==", + "version": "file:../bch-js", "requires": { "@chris.troutner/bip32-utils": "1.0.5", "@psf/bip21": "2.0.1", @@ -23344,6 +23327,8 @@ "@psf/coininfo": "4.0.0", "@uppy/core": "1.10.4", "@uppy/tus": "1.5.12", + "apidoc": "0.17.7", + "assert": "2.0.0", "axios": "^0.21.4", "bc-bip68": "1.0.5", "bchaddrjs-slp": "0.2.5", @@ -23355,37 +23340,30 @@ "bip66": "1.1.5", "bitcoinjs-message": "2.0.0", "bs58": "4.0.1", + "chai": "4.1.2", + "coveralls": "3.0.2", "ecashaddrjs": "1.0.7", + "eslint": "7.17.0", + "eslint-config-prettier": "7.1.0", + "eslint-config-standard": "16.0.3", + "eslint-plugin-node": "11.1.0", + "eslint-plugin-prettier": "3.3.1", + "eslint-plugin-standard": "4.0.0", + "husky": "^4.3.8", "ini": "1.3.8", + "lodash.clonedeep": "4.5.0", + "mocha": "9.1.3", + "node-mocks-http": "1.7.0", + "nyc": "15.1.0", "randombytes": "2.0.6", "safe-buffer": "5.1.2", "satoshi-bitcoin": "1.0.4", + "semantic-release": "18.0.0", + "sinon": "9.2.2", "slp-mdm": "0.0.6", "slp-parser": "0.0.4", + "standard": "^16.0.4", "wif": "2.0.6" - }, - "dependencies": { - "axios": { - "version": "0.21.4", - "resolved": "http://94.130.170.209:4873/axios/-/axios-0.21.4.tgz", - "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", - "requires": { - "follow-redirects": "^1.14.0" - } - }, - "randombytes": { - "version": "2.0.6", - "resolved": "http://94.130.170.209:4873/randombytes/-/randombytes-2.0.6.tgz", - "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", - "requires": { - "safe-buffer": "^5.1.0" - } - }, - "safe-buffer": { - "version": "5.1.2", - "resolved": "http://94.130.170.209:4873/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" - } } }, "@psf/bip21": { @@ -28099,9 +28077,9 @@ } }, "ipfs-coord": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/ipfs-coord/-/ipfs-coord-7.1.4.tgz", - "integrity": "sha512-YsCdrQ30JW03uOLNrpZKqNmWr5zmxF9AyKeXSQA/OTPBoOOqM2nomEYhTPVJjGxee9Qa2dtxCQzw3JwsLDCe4Q==", + "version": "7.1.6", + "resolved": "http://94.130.170.209:4873/ipfs-coord/-/ipfs-coord-7.1.6.tgz", + "integrity": "sha512-wtdJ/PgRMv3r2pOZ1WIc7blTkk3YftMbXTNX9hN9Lu+IblUhNoWUyjq7E4i7J3zBZQVQs7J3TyckRdLCekmc9A==", "requires": { "axios": "0.21.4", "bch-encrypt-lib": "2.0.0", @@ -31617,32 +31595,30 @@ "dev": true }, "minimal-slp-wallet": { - "version": "4.4.2", - "resolved": "http://94.130.170.209:4873/minimal-slp-wallet/-/minimal-slp-wallet-4.4.2.tgz", - "integrity": "sha512-6JNRnH8mQQb/gmfwdd0KgJeHFJycK60EDmeEdjghvm1I2J0z2HVI3gZzrVqjBC/tBDYKtuDuvRtix7q2LqKqhw==", + "version": "file:../minimal-slp-wallet", "requires": { "@psf/bch-js": "5.3.2", "apidoc": "0.25.0", "bch-consumer": "1.2.0", "bch-donation": "1.1.1", - "crypto-js": "4.0.0" - }, - "dependencies": { - "apidoc": { - "version": "0.25.0", - "resolved": "http://94.130.170.209:4873/apidoc/-/apidoc-0.25.0.tgz", - "integrity": "sha512-5g9fp8OffXZOdBTzm4BBvV5Vw54s+NmKnGZIUKuH+gRTqqJuRJpcGN6sz6WnjJ+NcvXhB7rIRp6FhtJahazx2Q==", - "requires": { - "apidoc-core": "^0.12.0", - "commander": "^2.20.0", - "fs-extra": "^9.0.1", - "handlebars": "^4.7.6", - "lodash": "^4.17.20", - "markdown-it": "^11.0.0", - "nodemon": "^2.0.4", - "winston": "^3.3.3" - } - } + "browserify": "17.0.0", + "chai": "4.2.0", + "coveralls": "3.1.0", + "crypto-js": "4.0.0", + "eslint": "7.17.0", + "eslint-config-prettier": "7.1.0", + "eslint-config-standard": "16.0.2", + "eslint-plugin-node": "11.1.0", + "eslint-plugin-prettier": "3.3.1", + "eslint-plugin-standard": "4.0.1", + "husky": "4.3.8", + "lodash.clonedeep": "4.5.0", + "mocha": "8.2.0", + "nyc": "15.1.0", + "semantic-release": "17.4.4", + "sinon": "9.2.0", + "standard": "16.0.4", + "tinyify": "3.0.0" } }, "minimalistic-assert": { @@ -34980,6 +34956,49 @@ "minimal-slp-wallet": "4.4.2" }, "dependencies": { + "@psf/bch-js": { + "version": "5.3.2", + "resolved": "http://94.130.170.209:4873/@psf%2fbch-js/-/bch-js-5.3.2.tgz", + "integrity": "sha512-LzYekwuee+T8AEZ1MKBe5KnZKXzpRZ57SpXRL2S8qsHefokTIN+XnDPX7dsOFkN4VGGgyjYyZ33iN6xhOSHWJw==", + "requires": { + "@chris.troutner/bip32-utils": "1.0.5", + "@psf/bip21": "2.0.1", + "@psf/bitcoincash-ops": "2.0.0", + "@psf/bitcoincashjs-lib": "4.0.2", + "@psf/coininfo": "4.0.0", + "@uppy/core": "1.10.4", + "@uppy/tus": "1.5.12", + "axios": "^0.21.4", + "bc-bip68": "1.0.5", + "bchaddrjs-slp": "0.2.5", + "bigi": "1.4.2", + "bignumber.js": "9.0.0", + "bip-schnorr": "0.3.0", + "bip38": "2.0.2", + "bip39": "3.0.2", + "bip66": "1.1.5", + "bitcoinjs-message": "2.0.0", + "bs58": "4.0.1", + "ecashaddrjs": "1.0.7", + "ini": "1.3.8", + "randombytes": "2.0.6", + "safe-buffer": "5.1.2", + "satoshi-bitcoin": "1.0.4", + "slp-mdm": "0.0.6", + "slp-parser": "0.0.4", + "wif": "2.0.6" + }, + "dependencies": { + "axios": { + "version": "0.21.4", + "resolved": "http://94.130.170.209:4873/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "requires": { + "follow-redirects": "^1.14.0" + } + } + } + }, "apidoc": { "version": "0.25.0", "resolved": "http://94.130.170.209:4873/apidoc/-/apidoc-0.25.0.tgz", @@ -35002,6 +35021,31 @@ "requires": { "follow-redirects": "^1.14.4" } + }, + "minimal-slp-wallet": { + "version": "4.4.2", + "resolved": "http://94.130.170.209:4873/minimal-slp-wallet/-/minimal-slp-wallet-4.4.2.tgz", + "integrity": "sha512-6JNRnH8mQQb/gmfwdd0KgJeHFJycK60EDmeEdjghvm1I2J0z2HVI3gZzrVqjBC/tBDYKtuDuvRtix7q2LqKqhw==", + "requires": { + "@psf/bch-js": "5.3.2", + "apidoc": "0.25.0", + "bch-consumer": "1.2.0", + "bch-donation": "1.1.1", + "crypto-js": "4.0.0" + } + }, + "randombytes": { + "version": "2.0.6", + "resolved": "http://94.130.170.209:4873/randombytes/-/randombytes-2.0.6.tgz", + "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "http://94.130.170.209:4873/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, diff --git a/package.json b/package.json index 6dfb5fd..41a5d37 100644 --- a/package.json +++ b/package.json @@ -27,13 +27,13 @@ }, "repository": "Permissionless-Software-Foundation/bch-dex", "dependencies": { - "@psf/bch-js": "5.3.2", + "@psf/bch-js": "../bch-js", "axios": "0.21.1", "bch-message-lib": "2.1.4", "bcryptjs": "2.4.3", "glob": "7.1.6", "ipfs": "0.60.0", - "ipfs-coord": "7.1.4", + "ipfs-coord": "7.1.6", "ipfs-http-client": "55.0.0", "jsonrpc-lite": "2.2.0", "jsonwebtoken": "8.5.1", @@ -51,7 +51,7 @@ "koa2-ratelimit": "0.9.1", "libp2p": "^0.36.2", "line-reader": "0.4.0", - "minimal-slp-wallet": "4.4.2", + "minimal-slp-wallet": "../minimal-slp-wallet", "mongoose": "5.13.13", "node-fetch": "npm:@achingbrain/node-fetch@2.6.7", "nodemailer": "6.4.17", diff --git a/src/adapters/wallet.js b/src/adapters/wallet.js index ff226b5..d1a775e 100644 --- a/src/adapters/wallet.js +++ b/src/adapters/wallet.js @@ -180,16 +180,128 @@ class WalletAdapter { // Generate a partial transcation to *take* a 'sell' order. async generatePartialTx (orderInfo) { - // try { - // // const bchjs = this.bchWallet.bchjs - // - // return true - // } catch (err) { - // console.error('Error in wallet.js/generatePartialTx()') - // throw err - // } + try { + console.log(`orderInfo: ${JSON.stringify(orderInfo, null, 2)}`) - return true + const bchjs = this.bchWallet.bchjs + + // instance of transaction builder + const transactionBuilder = new bchjs.TransactionBuilder() + + // Get a payment UTXO. + // TODO: Create a segregated UTXO. + const bchUtxos = this.bchWallet.utxos.utxoStore.bchUtxos + const paymentUtxo = await bchjs.Utxo.findBiggestUtxo(bchUtxos) + console.log('paymentUtxo: ', paymentUtxo) + + // Get token info on the offered UTXO + const txData = await this.bchWallet.getTxData([orderInfo.utxoTxid]) + console.log(`txData: ${JSON.stringify(txData, null, 2)}`) + + // Construct the UTXO being offered for sale. + const offeredUtxo = { + txid: orderInfo.utxoTxid, + vout: orderInfo.utxoVout, + tokenId: orderInfo.tokenId, + decimals: txData[0].tokenDecimals, + tokenQty: orderInfo.numTokens.toString() + } + console.log(`offeredUtxo: ${JSON.stringify(offeredUtxo, null, 2)}`) + + // Build First part of the collaborative Tx a.k.a. Alice + // Generate the OP_RETURN code. + const slpSendObj = bchjs.SLP.TokenType1.generateSendOpReturn( + [offeredUtxo], + orderInfo.numTokens.toString() + ) + const slpData = slpSendObj.script + console.log(`slpOutputs: ${slpSendObj.outputs}`) + + // Currently this app only supports a single SLP token UTXO for exact + // token quantities (no token change). e.g. 1 UTXO representing the + // exact number of 'numTokens' + if (slpSendObj.outputs > 1) { + console.log('WARNING: choose one UTXO with all tokens to exchange') + return + } + + // Calculate sats needed to pay the offer. + const satsNeeded = orderInfo.numTokens * parseInt(orderInfo.rateInSats) + + // Calculate miner fees. + // Get byte count (minimum 2 inputs, 3 outputs) + const opReturnBufLength = slpData.byteLength + 32 // add padding + const byteCount = + bchjs.BitcoinCash.getByteCount({ P2PKH: 2 }, { P2PKH: 4 }) + + opReturnBufLength + const totalSatsNeeded = byteCount + satsNeeded + // console.log(`satoshis needed: ${satsNeeded}`) + + // One last check to ensure the app wallet has enough BCH to complete + // the trade. + if (totalSatsNeeded > paymentUtxo.value) { + console.log(`Selected payment UTXO is not big enough. Sats needed: ${totalSatsNeeded}, UTXO value: ${paymentUtxo.value}`) + } + + // add UTXO for sell(STILL CANNOT SPEND - not signed yet) + transactionBuilder.addInput(orderInfo.utxoTxid, orderInfo.utxoVout) + + // add payment UTXO + transactionBuilder.addInput(paymentUtxo.tx_hash, paymentUtxo.tx_pos) + + const originalAmount = paymentUtxo.value + const dust = 546 + const remainder = originalAmount - satsNeeded - dust // exchange fee + token UTXO dust + + // Add the SLP OP_RETURN data as the first output. + transactionBuilder.addOutput(slpData, 0) + + const buyerAddr = this.bchWallet.walletInfo.legacyAddress + // console.log(`buyAddr: ${JSON.stringify(buyAddr, null, 2)}`) + + // Send dust transaction representing tokens being sent. + transactionBuilder.addOutput( + buyerAddr, + dust + ) + + // Get seller address + // TODO: Seller should explicitly define the address to send to in the + // orderInfo object. Right now it retrieves the address from the UTXO + // for sale, which is not ideal. + const sellerAddr = txData[0].vout[1].scriptPubKey.addresses[0] + + // Send payment to the offer side + transactionBuilder.addOutput(sellerAddr, satsNeeded) + + // Send the BCH change back to the buyer + if (remainder > 550) { + transactionBuilder.addOutput(buyerAddr, remainder) + } + + const buyerECPair = bchjs.ECPair.fromWIF(this.bchWallet.walletInfo.privateKey) + + // Sign the buyers input UTXO for spending. + transactionBuilder.sign( + 1, + buyerECPair, + null, + transactionBuilder.hashTypes.SIGHASH_ALL, + originalAmount + ) + + const tx = transactionBuilder.transaction.buildIncomplete() + + const hex = tx.toHex() + console.log('hex: ', hex) + + return hex + } catch (err) { + console.error('Error in wallet.js/generatePartialTx(): ', err) + throw err + } + + // return true } } diff --git a/src/use-cases/order/index.js b/src/use-cases/order/index.js index d8a1e40..1f0648e 100644 --- a/src/use-cases/order/index.js +++ b/src/use-cases/order/index.js @@ -107,11 +107,11 @@ class OrderUseCases { // Create a partially signed transaction. // https://github.com/Permissionless-Software-Foundation/bch-js-examples/blob/master/bch/applications/collaborate/sell-slp/e2e-exchange/step2-purchase-tx.js#L59 - // const partialTxHex = await this.adapaters.wallet.generatePartialTx(orderInfo) + const partialTxHex = await this.adapters.wallet.generatePartialTx(orderInfo) - // return partialTxHex + return partialTxHex - return true + // return true } catch (err) { console.error('Error in use-cases/order/takeOrder()') throw err From d62fc3b5755ffbafbf6954694ddab90323aa2ee4 Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Fri, 11 Mar 2022 08:15:31 -0800 Subject: [PATCH 7/8] Got /order/take mocked up for further development --- src/use-cases/order/index.js | 32 +++++++++++++++++++++++++++++--- 1 file changed, 29 insertions(+), 3 deletions(-) diff --git a/src/use-cases/order/index.js b/src/use-cases/order/index.js index 1f0648e..f1b54a7 100644 --- a/src/use-cases/order/index.js +++ b/src/use-cases/order/index.js @@ -8,7 +8,9 @@ local Offers are no different than Orders generated by other peers. */ +// Local libraries const OrderEntity = require('../../entities/order') +const config = require('../../../config') class OrderUseCases { constructor (localConfig = {}) { @@ -20,6 +22,9 @@ class OrderUseCases { ) } + // Encapsulate dependencies + this.config = config + this.orderEntity = new OrderEntity() this.OrderModel = this.adapters.localdb.Order } @@ -105,15 +110,34 @@ class OrderUseCases { const utxos = this.adapters.wallet.bchWallet.utxos.utxoStore console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`) + // TODO: Move funds to create a segrated UTXO for taking the order + // Create a partially signed transaction. // https://github.com/Permissionless-Software-Foundation/bch-js-examples/blob/master/bch/applications/collaborate/sell-slp/e2e-exchange/step2-purchase-tx.js#L59 const partialTxHex = await this.adapters.wallet.generatePartialTx(orderInfo) + // return partialTxHex - return partialTxHex + // Create valid Order object + const takenOrderInfo = Object.assign({}, orderInfo) + takenOrderInfo.patialTxHex = partialTxHex + delete takenOrderInfo.p2wdbHash + delete takenOrderInfo._id + takenOrderInfo.offerHash = orderInfo.p2wdbHash - // return true + // Write order info to the P2WDB + // TODO: This will trigger the webhook. Find some way of triggering the + // webhook on new orders, but not on counteroffers + const p2wdbObj = { + wif: this.adapters.wallet.bchWallet.walletInfo.privateKey, + data: takenOrderInfo, + appId: this.config.p2wdbAppId + } + const hash = await this.adapters.p2wdb.write(p2wdbObj) + + // Return the P2WDB CID + return hash } catch (err) { - console.error('Error in use-cases/order/takeOrder()') + console.error('Error in use-cases/order/takeOrder(): ', err) throw err } } @@ -125,6 +149,8 @@ class OrderUseCases { // console.log('this.adapters.wallet: ', this.adapters.wallet.bchWallet) // console.log(`walletInfo: ${JSON.stringify(this.adapters.wallet.bchWallet.walletInfo, null, 2)}`) + await this.adapters.wallet.bchWallet.walletInfoPromise + // Ensure the app wallet has enough funds to write to the P2WDB. const wif = this.adapters.wallet.bchWallet.walletInfo.privateKey const canWriteToP2WDB = await this.adapters.p2wdb.checkForSufficientFunds(wif) From 84dfd5691854e5d7b0253e0018b695182832e97d Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Fri, 11 Mar 2022 08:25:35 -0800 Subject: [PATCH 8/8] Fixing dependencies --- dev-docs/README.md | 6 +- package-lock.json | 200 ++++++++++++++++++++++++++++++++------------- package.json | 4 +- 3 files changed, 150 insertions(+), 60 deletions(-) diff --git a/dev-docs/README.md b/dev-docs/README.md index 99f2b03..62562dc 100644 --- a/dev-docs/README.md +++ b/dev-docs/README.md @@ -10,16 +10,16 @@ There are three major pieces of software behind the bch-dex concept. They work t ![bch-dex major subcomponents](./diagrams/software-interaction.png) -- _Client_ could be a web browser, or a command-line client like [psf-bch-wallet](https://github.com/Permissionless-Software-Foundation/psf-bch-wallet) or [psf-avax-wallet](https://github.com/Permissionless-Software-Foundation/psf-avax-wallet). +- _Client_ could be a web browser like [wallet.fullstack.cash](https://bchn-wallet.fullstack.cash), or a command-line client like [psf-bch-wallet](https://github.com/Permissionless-Software-Foundation/psf-bch-wallet) or [psf-avax-wallet](https://github.com/Permissionless-Software-Foundation/psf-avax-wallet). - [bch-dex](https://github.com/Permissionless-Software-Foundation/bch-dex) is the back end REST API that maintains a local database of information that the client reads from. - [P2WDB](https://github.com/Permissionless-Software-Foundation/ipfs-p2wdb-service) is the pay-to-write global database with a REST API for interfacing with the other two pieces of software. The arrows in the image represent the information flow between the three pieces of software: - The _Client_ is essentially a 'dummy terminal' with a bidirectional interface to bch-dex. bch-dex does the heavy lifting, and the _Client_ is a 'thin' UI wrapper. -- `bch-dex` imports data from the global P2WDB database into its local database, using a [webhook](https://en.wikipedia.org/wiki/Webhook) (dashed line). It can also custody funds, pay transaction fees, and create an _Offer_ by submitting the data to the P2WDB to generate an _Order_ (solid line). +- `bch-dex` imports data from the global P2WDB database into its local database, using a [webhook](https://en.wikipedia.org/wiki/Webhook) (dashed line). It can also custody funds, pay transaction fees, and create an _Order_ by submitting the data to the P2WDB to generate an _Offer_ (solid line). -This architecture keeps the global database highly censorship resistant, while allowing local installations to maintain tight control over the user experience. The goal is to have many redundant copies of `bch-dex` on the network, and to empower individual traders to run their own, private copy. +This architecture keeps the global database highly censorship resistant, while allowing local installations to maintain tight control over the user experience. The goal is to have many redundant copies of `bch-dex` on the network, and to empower individual traders to run their own, private copy, while maintaining a single source of truth via the P2WDB. # Back End diff --git a/package-lock.json b/package-lock.json index 6007d75..e002ed8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,7 +9,7 @@ "version": "1.0.0", "license": "MIT", "dependencies": { - "@psf/bch-js": "../bch-js", + "@psf/bch-js": "5.4.0", "axios": "0.21.1", "bch-message-lib": "2.1.4", "bcryptjs": "2.4.3", @@ -33,7 +33,7 @@ "koa2-ratelimit": "0.9.1", "libp2p": "^0.36.2", "line-reader": "0.4.0", - "minimal-slp-wallet": "../minimal-slp-wallet", + "minimal-slp-wallet": "4.4.3", "mongoose": "5.13.13", "node-fetch": "npm:@achingbrain/node-fetch@2.6.7", "nodemailer": "6.4.17", @@ -67,6 +67,7 @@ "../bch-js": { "name": "@psf/bch-js", "version": "5.0.0", + "extraneous": true, "license": "MIT", "dependencies": { "@chris.troutner/bip32-utils": "1.0.5", @@ -74,8 +75,6 @@ "@psf/bitcoincash-ops": "2.0.0", "@psf/bitcoincashjs-lib": "4.0.2", "@psf/coininfo": "4.0.0", - "@uppy/core": "1.10.4", - "@uppy/tus": "1.5.12", "axios": "^0.21.4", "bc-bip68": "1.0.5", "bchaddrjs-slp": "0.2.5", @@ -97,7 +96,7 @@ "wif": "2.0.6" }, "devDependencies": { - "apidoc": "0.17.7", + "apidoc": "^0.50.5", "assert": "2.0.0", "chai": "4.1.2", "coveralls": "3.0.2", @@ -109,19 +108,20 @@ "eslint-plugin-standard": "4.0.0", "husky": "^4.3.8", "lodash.clonedeep": "4.5.0", - "mocha": "9.1.3", + "mocha": "^9.2.1", "node-mocks-http": "1.7.0", "nyc": "15.1.0", - "semantic-release": "18.0.0", + "semantic-release": "^19.0.2", "sinon": "9.2.2", "standard": "^16.0.4" } }, "../minimal-slp-wallet": { "version": "4.0.0", + "extraneous": true, "license": "MIT", "dependencies": { - "@psf/bch-js": "5.3.2", + "@psf/bch-js": "../bch-js", "apidoc": "0.25.0", "bch-consumer": "1.2.0", "bch-donation": "1.1.1", @@ -139,9 +139,9 @@ "eslint-plugin-standard": "4.0.1", "husky": "4.3.8", "lodash.clonedeep": "4.5.0", - "mocha": "8.2.0", + "mocha": "^9.2.1", "nyc": "15.1.0", - "semantic-release": "17.4.4", + "semantic-release": "^19.0.2", "sinon": "9.2.0", "standard": "16.0.4", "tinyify": "3.0.0" @@ -1603,8 +1603,60 @@ "license": "BSD-3-Clause" }, "node_modules/@psf/bch-js": { - "resolved": "../bch-js", - "link": true + "version": "5.4.0", + "resolved": "http://94.130.170.209:4873/@psf%2fbch-js/-/bch-js-5.4.0.tgz", + "integrity": "sha512-HyBCpobC9moxSbQl4k/tb/GW9p8L12Fu+8yR1x8eflxBe7c2bwBlG218LmuoNcvjJMAdDxk0VEHvtO64vpltsw==", + "license": "MIT", + "dependencies": { + "@chris.troutner/bip32-utils": "1.0.5", + "@psf/bip21": "2.0.1", + "@psf/bitcoincash-ops": "2.0.0", + "@psf/bitcoincashjs-lib": "4.0.2", + "@psf/coininfo": "4.0.0", + "axios": "^0.21.4", + "bc-bip68": "1.0.5", + "bchaddrjs-slp": "0.2.5", + "bigi": "1.4.2", + "bignumber.js": "9.0.0", + "bip-schnorr": "0.3.0", + "bip38": "2.0.2", + "bip39": "3.0.2", + "bip66": "1.1.5", + "bitcoinjs-message": "2.0.0", + "bs58": "4.0.1", + "ecashaddrjs": "1.0.7", + "ini": "1.3.8", + "randombytes": "2.0.6", + "safe-buffer": "5.1.2", + "satoshi-bitcoin": "1.0.4", + "slp-mdm": "0.0.6", + "slp-parser": "0.0.4", + "wif": "2.0.6" + } + }, + "node_modules/@psf/bch-js/node_modules/axios": { + "version": "0.21.4", + "resolved": "http://94.130.170.209:4873/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, + "node_modules/@psf/bch-js/node_modules/randombytes": { + "version": "2.0.6", + "resolved": "http://94.130.170.209:4873/randombytes/-/randombytes-2.0.6.tgz", + "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", + "license": "MIT", + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/@psf/bch-js/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "http://94.130.170.209:4873/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" }, "node_modules/@psf/bip21": { "version": "2.0.1", @@ -12969,8 +13021,39 @@ } }, "node_modules/minimal-slp-wallet": { - "resolved": "../minimal-slp-wallet", - "link": true + "version": "4.4.3", + "resolved": "http://94.130.170.209:4873/minimal-slp-wallet/-/minimal-slp-wallet-4.4.3.tgz", + "integrity": "sha512-wpELTh4WYems96SxyRW/KdglJ6T3ieR7VVDCLcqq7bwZ+tJXFznOxDRggS5sREI9tTxiAHIyVSg+nbXZZk8buQ==", + "license": "MIT", + "dependencies": { + "@psf/bch-js": "5.4.0", + "apidoc": "0.25.0", + "bch-consumer": "1.2.0", + "bch-donation": "1.1.1", + "crypto-js": "4.0.0" + } + }, + "node_modules/minimal-slp-wallet/node_modules/apidoc": { + "version": "0.25.0", + "resolved": "http://94.130.170.209:4873/apidoc/-/apidoc-0.25.0.tgz", + "integrity": "sha512-5g9fp8OffXZOdBTzm4BBvV5Vw54s+NmKnGZIUKuH+gRTqqJuRJpcGN6sz6WnjJ+NcvXhB7rIRp6FhtJahazx2Q==", + "license": "MIT", + "dependencies": { + "apidoc-core": "^0.12.0", + "commander": "^2.20.0", + "fs-extra": "^9.0.1", + "handlebars": "^4.7.6", + "lodash": "^4.17.20", + "markdown-it": "^11.0.0", + "nodemon": "^2.0.4", + "winston": "^3.3.3" + }, + "bin": { + "apidoc": "bin/apidoc" + }, + "engines": { + "node": ">= 0.10.0" + } }, "node_modules/minimalistic-assert": { "version": "1.0.1", @@ -23318,17 +23401,15 @@ "version": "1.1.0" }, "@psf/bch-js": { - "version": "file:../bch-js", + "version": "5.4.0", + "resolved": "http://94.130.170.209:4873/@psf%2fbch-js/-/bch-js-5.4.0.tgz", + "integrity": "sha512-HyBCpobC9moxSbQl4k/tb/GW9p8L12Fu+8yR1x8eflxBe7c2bwBlG218LmuoNcvjJMAdDxk0VEHvtO64vpltsw==", "requires": { "@chris.troutner/bip32-utils": "1.0.5", "@psf/bip21": "2.0.1", "@psf/bitcoincash-ops": "2.0.0", "@psf/bitcoincashjs-lib": "4.0.2", "@psf/coininfo": "4.0.0", - "@uppy/core": "1.10.4", - "@uppy/tus": "1.5.12", - "apidoc": "0.17.7", - "assert": "2.0.0", "axios": "^0.21.4", "bc-bip68": "1.0.5", "bchaddrjs-slp": "0.2.5", @@ -23340,30 +23421,37 @@ "bip66": "1.1.5", "bitcoinjs-message": "2.0.0", "bs58": "4.0.1", - "chai": "4.1.2", - "coveralls": "3.0.2", "ecashaddrjs": "1.0.7", - "eslint": "7.17.0", - "eslint-config-prettier": "7.1.0", - "eslint-config-standard": "16.0.3", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "3.3.1", - "eslint-plugin-standard": "4.0.0", - "husky": "^4.3.8", "ini": "1.3.8", - "lodash.clonedeep": "4.5.0", - "mocha": "9.1.3", - "node-mocks-http": "1.7.0", - "nyc": "15.1.0", "randombytes": "2.0.6", "safe-buffer": "5.1.2", "satoshi-bitcoin": "1.0.4", - "semantic-release": "18.0.0", - "sinon": "9.2.2", "slp-mdm": "0.0.6", "slp-parser": "0.0.4", - "standard": "^16.0.4", "wif": "2.0.6" + }, + "dependencies": { + "axios": { + "version": "0.21.4", + "resolved": "http://94.130.170.209:4873/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "requires": { + "follow-redirects": "^1.14.0" + } + }, + "randombytes": { + "version": "2.0.6", + "resolved": "http://94.130.170.209:4873/randombytes/-/randombytes-2.0.6.tgz", + "integrity": "sha512-CIQ5OFxf4Jou6uOKe9t1AOgqpeU5fd70A8NPdHSGeYXqXsPe6peOwI0cUl88RWZ6sP1vPMV3avd/R6cZ5/sP1A==", + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "http://94.130.170.209:4873/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + } } }, "@psf/bip21": { @@ -31595,30 +31683,32 @@ "dev": true }, "minimal-slp-wallet": { - "version": "file:../minimal-slp-wallet", + "version": "4.4.3", + "resolved": "http://94.130.170.209:4873/minimal-slp-wallet/-/minimal-slp-wallet-4.4.3.tgz", + "integrity": "sha512-wpELTh4WYems96SxyRW/KdglJ6T3ieR7VVDCLcqq7bwZ+tJXFznOxDRggS5sREI9tTxiAHIyVSg+nbXZZk8buQ==", "requires": { - "@psf/bch-js": "5.3.2", + "@psf/bch-js": "5.4.0", "apidoc": "0.25.0", "bch-consumer": "1.2.0", "bch-donation": "1.1.1", - "browserify": "17.0.0", - "chai": "4.2.0", - "coveralls": "3.1.0", - "crypto-js": "4.0.0", - "eslint": "7.17.0", - "eslint-config-prettier": "7.1.0", - "eslint-config-standard": "16.0.2", - "eslint-plugin-node": "11.1.0", - "eslint-plugin-prettier": "3.3.1", - "eslint-plugin-standard": "4.0.1", - "husky": "4.3.8", - "lodash.clonedeep": "4.5.0", - "mocha": "8.2.0", - "nyc": "15.1.0", - "semantic-release": "17.4.4", - "sinon": "9.2.0", - "standard": "16.0.4", - "tinyify": "3.0.0" + "crypto-js": "4.0.0" + }, + "dependencies": { + "apidoc": { + "version": "0.25.0", + "resolved": "http://94.130.170.209:4873/apidoc/-/apidoc-0.25.0.tgz", + "integrity": "sha512-5g9fp8OffXZOdBTzm4BBvV5Vw54s+NmKnGZIUKuH+gRTqqJuRJpcGN6sz6WnjJ+NcvXhB7rIRp6FhtJahazx2Q==", + "requires": { + "apidoc-core": "^0.12.0", + "commander": "^2.20.0", + "fs-extra": "^9.0.1", + "handlebars": "^4.7.6", + "lodash": "^4.17.20", + "markdown-it": "^11.0.0", + "nodemon": "^2.0.4", + "winston": "^3.3.3" + } + } } }, "minimalistic-assert": { diff --git a/package.json b/package.json index 41a5d37..997aba0 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ }, "repository": "Permissionless-Software-Foundation/bch-dex", "dependencies": { - "@psf/bch-js": "../bch-js", + "@psf/bch-js": "5.4.0", "axios": "0.21.1", "bch-message-lib": "2.1.4", "bcryptjs": "2.4.3", @@ -51,7 +51,7 @@ "koa2-ratelimit": "0.9.1", "libp2p": "^0.36.2", "line-reader": "0.4.0", - "minimal-slp-wallet": "../minimal-slp-wallet", + "minimal-slp-wallet": "4.4.3", "mongoose": "5.13.13", "node-fetch": "npm:@achingbrain/node-fetch@2.6.7", "nodemailer": "6.4.17",