mirror of
https://github.com/Permissionless-Software-Foundation/bch-dex.git
synced 2026-09-22 01:02:00 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9e14f0a230 | ||
|
|
7e6b76703d | ||
|
|
5910620048 | ||
|
|
4201850389 | ||
|
|
449efd2a98 | ||
|
|
5af8a31242 | ||
|
|
65bffe1ef6 | ||
|
|
ceafb2820d | ||
|
|
90d1e68dae | ||
|
|
57dfad9215 | ||
|
|
212bc65c7e | ||
|
|
810d386dbc | ||
|
|
8ed1dbcffe | ||
|
|
1563a740aa |
+2
-2
@@ -94,13 +94,13 @@ class Server {
|
||||
try {
|
||||
try {
|
||||
// Delete an old webhook if it exists.
|
||||
await webhookLib.deleteWebhook(`http://localhost:${config.port}/offer`)
|
||||
await webhookLib.deleteWebhook(`http://localhost:${config.port}/p2wdb`)
|
||||
} catch (err) {
|
||||
/* exit quietly */
|
||||
// console.log('err deleting webhook: ', err)
|
||||
}
|
||||
|
||||
await webhookLib.createWebhook(`http://localhost:${config.port}/offer`)
|
||||
await webhookLib.createWebhook(`http://localhost:${config.port}/p2wdb`)
|
||||
console.log('Webhook created')
|
||||
} catch (error) {
|
||||
console.log('Webhook cant be created')
|
||||
|
||||
Vendored
+2
-1
@@ -35,7 +35,8 @@ module.exports = {
|
||||
useFullStackCash: process.env.USE_FULLSTACKCASH ? true : false,
|
||||
consumerUrl: process.env.CONSUMER_URL
|
||||
? process.env.CONSUMER_URL
|
||||
: 'https://free-bch.fullstack.cash',
|
||||
// : 'https://free-bch.fullstack.cash',
|
||||
: 'https://wa-usa-bch-consumer.fullstackcash.nl',
|
||||
|
||||
// P2WDB URL that will accept API calls from the p2wdb npm library.
|
||||
p2wdbUrl: process.env.P2WDB_URL ? process.env.P2WDB_URL : 'https://p2wdb.fullstack.cash',
|
||||
|
||||
@@ -34,6 +34,9 @@ Order entities have the following properties:
|
||||
- For AVAX, the min currency is nano-Avax.
|
||||
- for eCash, the min currency is bits.
|
||||
- _minUnitsToExchange_ - The minimum order size accepted.
|
||||
- _makerAddr_ - The address for the taker to send money to.
|
||||
- _p2wdbTxid_ - The TXID proof-of-burn used to add the order to the P2WDB.
|
||||
- _p2wdbHash_ - The CID used to identify the order entry in the P2WDB.
|
||||
|
||||
- Authentication Data:
|
||||
- _signature_ - A message signed by the address which created the order.
|
||||
@@ -70,8 +73,13 @@ Offer entities have the following properties:
|
||||
- For AVAX, the min currency is nano-Avax.
|
||||
- for eCash, the min currency is bits.
|
||||
- _minUnitsToExchange_ - The minimum order size accepted.
|
||||
- _makerAddr_ - The address for the taker to send money to.
|
||||
- _p2wdbTxid_ - The TXID proof-of-burn used to add the order to the P2WDB.
|
||||
- _p2wdbHash_ - The CID used to identify the order entry in the P2WDB.
|
||||
- _offerStatus_ - The state of the offer. When the data is added to the P2WDB, it gets a value of 'posted', but the database model internal to bch-dex can have the following properties:
|
||||
- *posted* - The offer is posted and can be countered by a taker.
|
||||
- *taken* - The offer was countered and accepted.
|
||||
- *dead* - The UTXO was spent outside the trade, which automatically makes the offer dead.
|
||||
|
||||
- Authentication Data:
|
||||
- _signature_ - A message signed by the address which created the order.
|
||||
|
||||
Generated
+104
-26
@@ -9,10 +9,11 @@
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@psf/bch-js": "5.4.0",
|
||||
"@psf/bch-js": "6.2.1",
|
||||
"axios": "0.21.1",
|
||||
"bch-message-lib": "2.1.4",
|
||||
"bcryptjs": "2.4.3",
|
||||
"bitcoincashjs-lib": "3.3.3",
|
||||
"glob": "7.1.6",
|
||||
"ipfs": "0.60.0",
|
||||
"ipfs-coord": "7.1.6",
|
||||
@@ -33,7 +34,7 @@
|
||||
"koa2-ratelimit": "0.9.1",
|
||||
"libp2p": "^0.36.2",
|
||||
"line-reader": "0.4.0",
|
||||
"minimal-slp-wallet": "4.4.3",
|
||||
"minimal-slp-wallet": "4.4.5",
|
||||
"mongoose": "5.13.13",
|
||||
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
|
||||
"nodemailer": "6.4.17",
|
||||
@@ -1603,9 +1604,9 @@
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@psf/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==",
|
||||
"version": "6.2.1",
|
||||
"resolved": "http://94.130.170.209:4873/@psf%2fbch-js/-/bch-js-6.2.1.tgz",
|
||||
"integrity": "sha512-sTqjbPkpfxrxu0WARZBf/t2K+Sz3Y4EpuzHlSZG2n4k7jKM+J8tQuh/stRsEXOl/ewwdVmjNIAcBx+Z+MkCtHQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@chris.troutner/bip32-utils": "1.0.5",
|
||||
@@ -1613,7 +1614,7 @@
|
||||
"@psf/bitcoincash-ops": "2.0.0",
|
||||
"@psf/bitcoincashjs-lib": "4.0.2",
|
||||
"@psf/coininfo": "4.0.0",
|
||||
"axios": "^0.21.4",
|
||||
"axios": "0.26.1",
|
||||
"bc-bip68": "1.0.5",
|
||||
"bchaddrjs-slp": "0.2.5",
|
||||
"bigi": "1.4.2",
|
||||
@@ -1635,12 +1636,12 @@
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"version": "0.26.1",
|
||||
"resolved": "http://94.130.170.209:4873/axios/-/axios-0.26.1.tgz",
|
||||
"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.0"
|
||||
"follow-redirects": "^1.14.8"
|
||||
}
|
||||
},
|
||||
"node_modules/@psf/bch-js/node_modules/randombytes": {
|
||||
@@ -3113,6 +3114,39 @@
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"node_modules/bitcoin-ops": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "http://94.130.170.209:4873/bitcoin-ops/-/bitcoin-ops-1.4.1.tgz",
|
||||
"integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/bitcoincashjs-lib": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "http://94.130.170.209:4873/bitcoincashjs-lib/-/bitcoincashjs-lib-3.3.3.tgz",
|
||||
"integrity": "sha512-dk6GCVpCE+7oELy8l2Y/9vGR1ymlnsQffU7YmYNeBoUSTH9gckbKSk6xrg3O5b2P0C7+1SifQ8de7luCpq6k8g==",
|
||||
"deprecated": "ooops",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bech32": "^1.1.2",
|
||||
"bigi": "^1.4.0",
|
||||
"bip66": "^1.1.0",
|
||||
"bitcoin-ops": "^1.3.0",
|
||||
"bs58check": "^2.0.0",
|
||||
"create-hash": "^1.1.0",
|
||||
"create-hmac": "^1.1.3",
|
||||
"ecurve": "^1.0.0",
|
||||
"merkle-lib": "^2.0.10",
|
||||
"pushdata-bitcoin": "^1.0.1",
|
||||
"randombytes": "^2.0.1",
|
||||
"safe-buffer": "^5.0.1",
|
||||
"typeforce": "^1.11.3",
|
||||
"varuint-bitcoin": "^1.0.4",
|
||||
"wif": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bitcoinjs-message": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "http://94.130.170.209:4873/bitcoinjs-message/-/bitcoinjs-message-2.0.0.tgz",
|
||||
@@ -13021,12 +13055,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/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==",
|
||||
"version": "4.4.5",
|
||||
"resolved": "http://94.130.170.209:4873/minimal-slp-wallet/-/minimal-slp-wallet-4.4.5.tgz",
|
||||
"integrity": "sha512-f2Se66WAdVzPFwmsIRU2ltVLcjWYLDaAC5amlkEyk6lfkj5VEXrYq3480iz/BNRYWwtrTq63oKqMyXpJ7w9HWA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@psf/bch-js": "5.4.0",
|
||||
"@psf/bch-js": "6.2.1",
|
||||
"apidoc": "0.25.0",
|
||||
"bch-consumer": "1.2.0",
|
||||
"bch-donation": "1.1.1",
|
||||
@@ -18841,6 +18875,15 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/pushdata-bitcoin": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "http://94.130.170.209:4873/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz",
|
||||
"integrity": "sha1-FZMdPNlnreUiBvUjqnMxrvfUOvc=",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bitcoin-ops": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/q": {
|
||||
"version": "1.5.1",
|
||||
"dev": true,
|
||||
@@ -23401,16 +23444,16 @@
|
||||
"version": "1.1.0"
|
||||
},
|
||||
"@psf/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==",
|
||||
"version": "6.2.1",
|
||||
"resolved": "http://94.130.170.209:4873/@psf%2fbch-js/-/bch-js-6.2.1.tgz",
|
||||
"integrity": "sha512-sTqjbPkpfxrxu0WARZBf/t2K+Sz3Y4EpuzHlSZG2n4k7jKM+J8tQuh/stRsEXOl/ewwdVmjNIAcBx+Z+MkCtHQ==",
|
||||
"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",
|
||||
"axios": "^0.21.4",
|
||||
"axios": "0.26.1",
|
||||
"bc-bip68": "1.0.5",
|
||||
"bchaddrjs-slp": "0.2.5",
|
||||
"bigi": "1.4.2",
|
||||
@@ -23432,11 +23475,11 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": {
|
||||
"version": "0.21.4",
|
||||
"resolved": "http://94.130.170.209:4873/axios/-/axios-0.21.4.tgz",
|
||||
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
||||
"version": "0.26.1",
|
||||
"resolved": "http://94.130.170.209:4873/axios/-/axios-0.26.1.tgz",
|
||||
"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.14.0"
|
||||
"follow-redirects": "^1.14.8"
|
||||
}
|
||||
},
|
||||
"randombytes": {
|
||||
@@ -24510,6 +24553,33 @@
|
||||
"safe-buffer": "^5.0.1"
|
||||
}
|
||||
},
|
||||
"bitcoin-ops": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "http://94.130.170.209:4873/bitcoin-ops/-/bitcoin-ops-1.4.1.tgz",
|
||||
"integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow=="
|
||||
},
|
||||
"bitcoincashjs-lib": {
|
||||
"version": "3.3.3",
|
||||
"resolved": "http://94.130.170.209:4873/bitcoincashjs-lib/-/bitcoincashjs-lib-3.3.3.tgz",
|
||||
"integrity": "sha512-dk6GCVpCE+7oELy8l2Y/9vGR1ymlnsQffU7YmYNeBoUSTH9gckbKSk6xrg3O5b2P0C7+1SifQ8de7luCpq6k8g==",
|
||||
"requires": {
|
||||
"bech32": "^1.1.2",
|
||||
"bigi": "^1.4.0",
|
||||
"bip66": "^1.1.0",
|
||||
"bitcoin-ops": "^1.3.0",
|
||||
"bs58check": "^2.0.0",
|
||||
"create-hash": "^1.1.0",
|
||||
"create-hmac": "^1.1.3",
|
||||
"ecurve": "^1.0.0",
|
||||
"merkle-lib": "^2.0.10",
|
||||
"pushdata-bitcoin": "^1.0.1",
|
||||
"randombytes": "^2.0.1",
|
||||
"safe-buffer": "^5.0.1",
|
||||
"typeforce": "^1.11.3",
|
||||
"varuint-bitcoin": "^1.0.4",
|
||||
"wif": "^2.0.1"
|
||||
}
|
||||
},
|
||||
"bitcoinjs-message": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "http://94.130.170.209:4873/bitcoinjs-message/-/bitcoinjs-message-2.0.0.tgz",
|
||||
@@ -31683,11 +31753,11 @@
|
||||
"dev": true
|
||||
},
|
||||
"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==",
|
||||
"version": "4.4.5",
|
||||
"resolved": "http://94.130.170.209:4873/minimal-slp-wallet/-/minimal-slp-wallet-4.4.5.tgz",
|
||||
"integrity": "sha512-f2Se66WAdVzPFwmsIRU2ltVLcjWYLDaAC5amlkEyk6lfkj5VEXrYq3480iz/BNRYWwtrTq63oKqMyXpJ7w9HWA==",
|
||||
"requires": {
|
||||
"@psf/bch-js": "5.4.0",
|
||||
"@psf/bch-js": "6.2.1",
|
||||
"apidoc": "0.25.0",
|
||||
"bch-consumer": "1.2.0",
|
||||
"bch-donation": "1.1.1",
|
||||
@@ -35681,6 +35751,14 @@
|
||||
"escape-goat": "^2.0.0"
|
||||
}
|
||||
},
|
||||
"pushdata-bitcoin": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "http://94.130.170.209:4873/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz",
|
||||
"integrity": "sha1-FZMdPNlnreUiBvUjqnMxrvfUOvc=",
|
||||
"requires": {
|
||||
"bitcoin-ops": "^1.3.0"
|
||||
}
|
||||
},
|
||||
"q": {
|
||||
"version": "1.5.1",
|
||||
"dev": true
|
||||
|
||||
+5
-4
@@ -6,9 +6,9 @@
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"test": "npm run test:all",
|
||||
"test:all": "export BCH_DEX=test && nyc --reporter=text mocha --exit --timeout 15000 --recursive test/unit test/e2e/automated/",
|
||||
"test:all": "export BCH_DEX=test && nyc --reporter=text mocha --exit --timeout 30000 --recursive test/unit test/e2e/automated/",
|
||||
"test:unit": "export BCH_DEX=test && mocha --exit --timeout 15000 --recursive test/unit/",
|
||||
"test:e2e:auto": "export BCH_DEX=test && mocha --exit --timeout 15000 test/e2e/automated/",
|
||||
"test:e2e:auto": "export BCH_DEX=test && mocha --exit --timeout 30000 test/e2e/automated/",
|
||||
"test:integration": "export BCH_DEX=test && mocha --exit --timeout 45000 --recursive test/integration",
|
||||
"test:temp": "export BCH_DEX=test && mocha --exit --timeout 15000 -g '#rate-limit' test/unit/json-rpc/",
|
||||
"lint": "standard --env mocha --fix",
|
||||
@@ -27,10 +27,11 @@
|
||||
},
|
||||
"repository": "Permissionless-Software-Foundation/bch-dex",
|
||||
"dependencies": {
|
||||
"@psf/bch-js": "5.4.0",
|
||||
"@psf/bch-js": "6.2.1",
|
||||
"axios": "0.21.1",
|
||||
"bch-message-lib": "2.1.4",
|
||||
"bcryptjs": "2.4.3",
|
||||
"bitcoincashjs-lib": "3.3.3",
|
||||
"glob": "7.1.6",
|
||||
"ipfs": "0.60.0",
|
||||
"ipfs-coord": "7.1.6",
|
||||
@@ -51,7 +52,7 @@
|
||||
"koa2-ratelimit": "0.9.1",
|
||||
"libp2p": "^0.36.2",
|
||||
"line-reader": "0.4.0",
|
||||
"minimal-slp-wallet": "4.4.3",
|
||||
"minimal-slp-wallet": "4.4.5",
|
||||
"mongoose": "5.13.13",
|
||||
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
|
||||
"nodemailer": "6.4.17",
|
||||
|
||||
+24
-24
@@ -40,30 +40,30 @@ class Bch {
|
||||
}
|
||||
|
||||
// Gets the total psf token balance
|
||||
async getPSFTokenBalance (slpAddress) {
|
||||
try {
|
||||
if (!slpAddress || typeof slpAddress !== 'string') {
|
||||
throw new Error('slpAddress must be a string')
|
||||
}
|
||||
let psfBalance = 0
|
||||
const balances = await this.bchjs.SLP.Utils.balancesForAddress(
|
||||
slpAddress
|
||||
)
|
||||
|
||||
// Sums all the balances of all tokens
|
||||
// that match the psf token ID
|
||||
for (let i = 0; i < balances.length; i++) {
|
||||
if (balances[i].tokenId === this.PSF_TOKEN_ID) {
|
||||
psfBalance += balances[i].balance
|
||||
}
|
||||
}
|
||||
|
||||
return psfBalance
|
||||
} catch (err) {
|
||||
console.error('Error in bch.js/getPSFTokenBalance()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
// async getPSFTokenBalance (slpAddress) {
|
||||
// try {
|
||||
// if (!slpAddress || typeof slpAddress !== 'string') {
|
||||
// throw new Error('slpAddress must be a string')
|
||||
// }
|
||||
// let psfBalance = 0
|
||||
// const balances = await this.bchjs.SLP.Utils.balancesForAddress(
|
||||
// slpAddress
|
||||
// )
|
||||
//
|
||||
// // Sums all the balances of all tokens
|
||||
// // that match the psf token ID
|
||||
// for (let i = 0; i < balances.length; i++) {
|
||||
// if (balances[i].tokenId === this.PSF_TOKEN_ID) {
|
||||
// psfBalance += balances[i].balance
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// return psfBalance
|
||||
// } catch (err) {
|
||||
// console.error('Error in bch.js/getPSFTokenBalance()')
|
||||
// throw err
|
||||
// }
|
||||
// }
|
||||
|
||||
async getMerit (slpAddr) {
|
||||
try {
|
||||
|
||||
@@ -14,6 +14,8 @@ const Offer = new mongoose.Schema({
|
||||
minUnitsToExchange: { type: String },
|
||||
p2wdbTxid: { type: String },
|
||||
p2wdbHash: { type: String },
|
||||
offerStatus: { type: String },
|
||||
makerAddr: { type: String },
|
||||
|
||||
// Authentication data
|
||||
signature: { type: String },
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
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.
|
||||
|
||||
See the dev-docs/specification.md for details on each property.
|
||||
*/
|
||||
|
||||
const mongoose = require('mongoose')
|
||||
@@ -19,6 +21,7 @@ const Order = new mongoose.Schema({
|
||||
minUnitsToExchange: { type: String },
|
||||
p2wdbTxid: { type: String },
|
||||
p2wdbHash: { type: String },
|
||||
makerAddr: { type: String },
|
||||
|
||||
// Authentication data
|
||||
signature: { type: String },
|
||||
|
||||
+204
-39
@@ -4,6 +4,7 @@
|
||||
|
||||
// Public npm libraries
|
||||
const BchWallet = require('minimal-slp-wallet/index')
|
||||
const bitcoinJs = require('bitcoincashjs-lib')
|
||||
|
||||
// Local libraries
|
||||
const JsonFiles = require('./json-files')
|
||||
@@ -21,6 +22,7 @@ class WalletAdapter {
|
||||
this.WALLET_FILE = WALLET_FILE
|
||||
this.BchWallet = BchWallet
|
||||
this.config = config
|
||||
this.bitcoinJs = bitcoinJs
|
||||
}
|
||||
|
||||
// Open the wallet file, or create one if the file doesn't exist.
|
||||
@@ -84,6 +86,7 @@ class WalletAdapter {
|
||||
|
||||
// Wait for wallet to initialize.
|
||||
await this.bchWallet.walletInfoPromise
|
||||
console.log('BCH wallet initialized.')
|
||||
|
||||
return this.bchWallet
|
||||
} catch (err) {
|
||||
@@ -122,9 +125,12 @@ class WalletAdapter {
|
||||
// and the index of the HD wallet that the key pair was generated from.
|
||||
// TODO: Allow input integer. If input is used, use that as the index. If no
|
||||
// input is provided, then call incrementNextAddress().
|
||||
async getKeyPair () {
|
||||
async getKeyPair (hdIndex = 0) {
|
||||
try {
|
||||
const hdIndex = await this.incrementNextAddress()
|
||||
if (!hdIndex) {
|
||||
// Increment the HD index and generate a new key pair.
|
||||
hdIndex = await this.incrementNextAddress()
|
||||
}
|
||||
|
||||
const mnemonic = this.bchWallet.walletInfo.mnemonic
|
||||
|
||||
@@ -178,10 +184,10 @@ class WalletAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
// Generate a partial transcation to *take* a 'sell' order.
|
||||
async generatePartialTx (orderInfo) {
|
||||
// Generate a partial transcation to *take* a 'sell' offer.
|
||||
async generatePartialTx (offerInfo, utxoInfo) {
|
||||
try {
|
||||
console.log(`orderInfo: ${JSON.stringify(orderInfo, null, 2)}`)
|
||||
console.log(`offerInfo: ${JSON.stringify(offerInfo, null, 2)}`)
|
||||
|
||||
const bchjs = this.bchWallet.bchjs
|
||||
|
||||
@@ -190,21 +196,21 @@ class WalletAdapter {
|
||||
|
||||
// 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)
|
||||
// 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)}`)
|
||||
const txData = await this.bchWallet.getTxData([offerInfo.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,
|
||||
txid: offerInfo.utxoTxid,
|
||||
vout: offerInfo.utxoVout,
|
||||
tokenId: offerInfo.tokenId,
|
||||
decimals: txData[0].tokenDecimals,
|
||||
tokenQty: orderInfo.numTokens.toString()
|
||||
tokenQty: offerInfo.numTokens.toString()
|
||||
}
|
||||
console.log(`offeredUtxo: ${JSON.stringify(offeredUtxo, null, 2)}`)
|
||||
|
||||
@@ -212,7 +218,7 @@ class WalletAdapter {
|
||||
// Generate the OP_RETURN code.
|
||||
const slpSendObj = bchjs.SLP.TokenType1.generateSendOpReturn(
|
||||
[offeredUtxo],
|
||||
orderInfo.numTokens.toString()
|
||||
offerInfo.numTokens.toString()
|
||||
)
|
||||
const slpData = slpSendObj.script
|
||||
console.log(`slpOutputs: ${slpSendObj.outputs}`)
|
||||
@@ -226,32 +232,34 @@ class WalletAdapter {
|
||||
}
|
||||
|
||||
// Calculate sats needed to pay the offer.
|
||||
const satsNeeded = orderInfo.numTokens * parseInt(orderInfo.rateInSats)
|
||||
const satsNeeded = offerInfo.numTokens * parseInt(offerInfo.rateInBaseUnit)
|
||||
if (isNaN(satsNeeded)) throw new Error('Can not calculate needed sats')
|
||||
|
||||
// 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
|
||||
// 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}`)
|
||||
}
|
||||
// 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 UTXO for sale (STILL CANNOT SPEND - not signed yet)
|
||||
transactionBuilder.addInput(offerInfo.utxoTxid, offerInfo.utxoVout)
|
||||
|
||||
// add payment UTXO
|
||||
transactionBuilder.addInput(paymentUtxo.tx_hash, paymentUtxo.tx_pos)
|
||||
// transactionBuilder.addInput(paymentUtxo.tx_hash, paymentUtxo.tx_pos)
|
||||
transactionBuilder.addInput(utxoInfo.txid, utxoInfo.vout)
|
||||
|
||||
const originalAmount = paymentUtxo.value
|
||||
// const originalAmount = paymentUtxo.value
|
||||
const dust = 546
|
||||
const remainder = originalAmount - satsNeeded - dust // exchange fee + token UTXO dust
|
||||
// const remainder = originalAmount - satsNeeded - dust // exchange fee + token UTXO dust
|
||||
|
||||
// Add the SLP OP_RETURN data as the first output.
|
||||
transactionBuilder.addOutput(slpData, 0)
|
||||
@@ -266,20 +274,18 @@ class WalletAdapter {
|
||||
)
|
||||
|
||||
// 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]
|
||||
const sellerAddr = offerInfo.makerAddr
|
||||
|
||||
// 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)
|
||||
}
|
||||
// if (remainder > 550) {
|
||||
// transactionBuilder.addOutput(buyerAddr, remainder)
|
||||
// }
|
||||
|
||||
const buyerECPair = bchjs.ECPair.fromWIF(this.bchWallet.walletInfo.privateKey)
|
||||
// const buyerECPair = bchjs.ECPair.fromWIF(this.bchWallet.walletInfo.privateKey)
|
||||
const buyerECPair = bchjs.ECPair.fromWIF(utxoInfo.wif)
|
||||
|
||||
// Sign the buyers input UTXO for spending.
|
||||
transactionBuilder.sign(
|
||||
@@ -287,13 +293,13 @@ class WalletAdapter {
|
||||
buyerECPair,
|
||||
null,
|
||||
transactionBuilder.hashTypes.SIGHASH_ALL,
|
||||
originalAmount
|
||||
utxoInfo.sats
|
||||
)
|
||||
|
||||
const tx = transactionBuilder.transaction.buildIncomplete()
|
||||
|
||||
const hex = tx.toHex()
|
||||
console.log('hex: ', hex)
|
||||
// console.log('hex: ', hex)
|
||||
|
||||
return hex
|
||||
} catch (err) {
|
||||
@@ -303,6 +309,165 @@ class WalletAdapter {
|
||||
|
||||
// return true
|
||||
}
|
||||
|
||||
// Move tokens to an address controlled by the HD wallet, to generate a
|
||||
// segregated UTXO.
|
||||
async moveTokens (inObj = {}) {
|
||||
try {
|
||||
const { tokenId, qty } = inObj
|
||||
|
||||
const keyPair = await this.getKeyPair()
|
||||
console.log('keyPair: ', keyPair)
|
||||
|
||||
const receiver = {
|
||||
address: keyPair.cashAddress,
|
||||
tokenId,
|
||||
qty
|
||||
}
|
||||
|
||||
// Update the UTXO store of the wallet.
|
||||
await this.bchWallet.getUtxos()
|
||||
|
||||
const txid = await this.bchWallet.sendTokens(receiver, 3)
|
||||
|
||||
const utxoInfo = {
|
||||
txid,
|
||||
vout: 1,
|
||||
hdIndex: keyPair.hdIndex
|
||||
}
|
||||
|
||||
return utxoInfo
|
||||
} catch (err) {
|
||||
console.error('Error in wallet.js/moveTokens()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Move a quanity of BCH to an address controlled by the HD wallet, to
|
||||
// generate a segregated UTXO.
|
||||
async moveBch (amountSat) {
|
||||
try {
|
||||
const keyPair = await this.getKeyPair()
|
||||
console.log('keyPair: ', keyPair)
|
||||
|
||||
const receivers = [{
|
||||
address: keyPair.cashAddress,
|
||||
amountSat
|
||||
}]
|
||||
console.log(`receivers: ${JSON.stringify(receivers, null, 2)}`)
|
||||
|
||||
// Update the UTXO store of the wallet.
|
||||
await this.bchWallet.getUtxos()
|
||||
|
||||
const txid = await this.bchWallet.send(receivers)
|
||||
|
||||
const utxoInfo = {
|
||||
txid,
|
||||
vout: 0,
|
||||
hdIndex: keyPair.hdIndex,
|
||||
wif: keyPair.wif
|
||||
}
|
||||
|
||||
return utxoInfo
|
||||
} catch (err) {
|
||||
console.error('Error in wallet.js/moveBch()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Deserialize a hex string representing a partial TX. Returns an object
|
||||
// representing the transaction.
|
||||
async deseralizeTx (txHex) {
|
||||
try {
|
||||
// Ensure the URL points at FullStack.cash, since the web 3 infra does not
|
||||
// yet support this call.
|
||||
const oldUrl = this.bchWallet.bchjs.RawTransactions.restURL
|
||||
this.bchWallet.bchjs.RawTransactions.restURL = 'https://api.fullstack.cash/v5/'
|
||||
|
||||
// Use a full node to deserialize the transaction.
|
||||
const txObj2 = await this.bchWallet.bchjs.RawTransactions.decodeRawTransaction(txHex)
|
||||
// console.log(`txObj2: ${JSON.stringify(txObj2, null, 2)}`)
|
||||
|
||||
// Restore the old URL.
|
||||
this.bchWallet.bchjs.RawTransactions.restURL = oldUrl
|
||||
|
||||
return txObj2
|
||||
} catch (err) {
|
||||
console.error('Error in wallet.js/deserializePartialTx()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Complete the partially signed transaction by signing the first input,
|
||||
// then broadcasting the transaction to the network.
|
||||
async completeTx (hex, hdIndex) {
|
||||
try {
|
||||
// console.log('hex: ', hex)
|
||||
|
||||
const bchjs = this.bchWallet.bchjs
|
||||
|
||||
// instance of transaction builder
|
||||
// const transactionBuilder = new bchjs.TransactionBuilder()
|
||||
|
||||
// Convert the hex string version of the transaction into a Buffer.
|
||||
// const paymentBuffer = Buffer.from(hex, 'hex')
|
||||
|
||||
// Generate a Transaction object from the transaction binary data.
|
||||
// const csTransaction = this.bitcoinJs.Transaction.fromBuffer(paymentBuffer)
|
||||
const csTransaction = this.bitcoinJs.Transaction.fromHex(hex)
|
||||
// console.log(`payment tx: ${JSON.stringify(csTransaction, null, 2)}`)
|
||||
|
||||
// Instantiate the Transaction Builder.
|
||||
const csTransactionBuilder = this.bitcoinJs.TransactionBuilder.fromTransaction(
|
||||
csTransaction,
|
||||
'mainnet'
|
||||
)
|
||||
// const csTransactionBuilder = bchjs.TransactionBuilder.fromTransaction(
|
||||
// csTransaction,
|
||||
// 'mainnet'
|
||||
// )
|
||||
|
||||
// Get the keypair for the address used in the Order
|
||||
const keyPair = await this.getKeyPair(hdIndex)
|
||||
console.log(`maker keyPair: ${JSON.stringify(keyPair, null, 2)}`)
|
||||
const makerECPair = bchjs.ECPair.fromWIF(keyPair.wif)
|
||||
|
||||
// Assumption: segregated UTXO has a value of 546 sats. It might be better
|
||||
// to explicitly look this data up via the blockchain.
|
||||
const dust = 546
|
||||
|
||||
// Coutnersign the Maker's input, representing the tokens for sale.
|
||||
csTransactionBuilder.sign(
|
||||
0,
|
||||
makerECPair,
|
||||
null,
|
||||
this.bitcoinJs.Transaction.SIGHASH_ALL,
|
||||
// csTransactionBuilder.hashTypes.SIGHASH_ALL,
|
||||
dust
|
||||
)
|
||||
|
||||
// build tx
|
||||
const csTx = csTransactionBuilder.build()
|
||||
|
||||
// output rawhex
|
||||
const csTxHex = csTx.toHex()
|
||||
console.log(`Fully signed Tx hex: ${csTxHex}`)
|
||||
|
||||
// Debug: Display the fully signed TX
|
||||
const txObj = await this.deseralizeTx(csTxHex)
|
||||
console.log(`Final tx: ${JSON.stringify(txObj, null, 2)}`)
|
||||
|
||||
// return csTxHex
|
||||
|
||||
// Broadcast transaction to the network
|
||||
const txid = await this.bchWallet.ar.sendTx(csTxHex)
|
||||
|
||||
return txid
|
||||
} catch (err) {
|
||||
console.error('Error in wallet.js/completeTx()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WalletAdapter
|
||||
|
||||
@@ -14,6 +14,7 @@ const LogsRESTController = require('./logs')
|
||||
const EntryRouter = require('./entry')
|
||||
const OfferRouter = require('./offer')
|
||||
const OrderRouter = require('./order')
|
||||
const P2WDBRouter = require('./p2wdb')
|
||||
|
||||
class RESTControllers {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -65,6 +66,9 @@ class RESTControllers {
|
||||
|
||||
const orderRouter = new OrderRouter(dependencies)
|
||||
orderRouter.attach(app)
|
||||
|
||||
const p2wdbRouter = new P2WDBRouter(dependencies)
|
||||
p2wdbRouter.attach(app)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
REST API Controller library for the /offer route
|
||||
*/
|
||||
|
||||
// const { wlogger } = require('../../../adapters/wlogger')
|
||||
const { wlogger } = require('../../../adapters/wlogger')
|
||||
|
||||
let _this
|
||||
|
||||
@@ -65,19 +65,15 @@ class OfferRESTControllerLib {
|
||||
// listed in the offer.
|
||||
async takeOffer (ctx) {
|
||||
try {
|
||||
console.log('body: ', ctx.request.body)
|
||||
console.log('REST API controller, 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.')
|
||||
wlogger.error('Error in takeOffer() REST API handler.')
|
||||
_this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
/*
|
||||
REST API Controller library for the /p2wdb route
|
||||
This route handles incoming data from the P2WDB webhook, and routes the data
|
||||
to the proper handler.
|
||||
*/
|
||||
|
||||
// const { wlogger } = require('../../../adapters/wlogger')
|
||||
|
||||
let _this
|
||||
|
||||
class P2WDBRESTControllerLib {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating /p2wdb REST Controller.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating /p2wdb REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
// this.OrderModel = this.adapters.localdb.Order
|
||||
// this.userUseCases = this.useCases.user
|
||||
|
||||
_this = this
|
||||
}
|
||||
|
||||
// No api-doc documentation because this wont be a public endpoint
|
||||
async routeWebhook (ctx) {
|
||||
try {
|
||||
console.log('p2wdb REST API handler: body: ', ctx.request.body)
|
||||
|
||||
const dataType = ctx.request.body.data.dataType
|
||||
|
||||
if (dataType.includes('counter')) {
|
||||
// Detect and handle Counter Offers
|
||||
console.log('counter-offer data detected.')
|
||||
|
||||
const counterOffer = ctx.request.body
|
||||
|
||||
await _this.useCases.offer.acceptCounterOffer(counterOffer)
|
||||
} else if (dataType.includes('offer')) {
|
||||
// Detect and handle new Offers
|
||||
console.log('offer data detected')
|
||||
|
||||
const offerObj = ctx.request.body
|
||||
await _this.useCases.offer.createOffer(offerObj)
|
||||
} else {
|
||||
console.log('Could not route P2WDB webhook data.')
|
||||
}
|
||||
|
||||
// const orderObj = ctx.request.body.order
|
||||
//
|
||||
// const hash = await _this.useCases.order.createOrder(orderObj)
|
||||
//
|
||||
// ctx.body = { hash }
|
||||
|
||||
ctx.body = {
|
||||
success: true
|
||||
}
|
||||
} catch (err) {
|
||||
// console.log(`err.message: ${err.message}`)
|
||||
// console.log('err: ', err)
|
||||
// ctx.throw(422, err.message)
|
||||
_this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// DRY error handler
|
||||
handleError (ctx, err) {
|
||||
console.log('err', err)
|
||||
|
||||
// If an HTTP status is specified by the buisiness logic, use that.
|
||||
if (err.status) {
|
||||
if (err.message) {
|
||||
ctx.throw(err.status, err.message)
|
||||
} else {
|
||||
ctx.throw(err.status)
|
||||
}
|
||||
} else {
|
||||
// By default use a 422 error if the HTTP status is not specified.
|
||||
ctx.throw(422, err.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = P2WDBRESTControllerLib
|
||||
@@ -0,0 +1,62 @@
|
||||
/*
|
||||
REST API library for /p2wdb route.
|
||||
This route handles incoming data from the P2WDB webhook, and routes the data
|
||||
to the proper handler.
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const Router = require('koa-router')
|
||||
|
||||
// Local libraries.
|
||||
const P2WDBRESTControllerLib = require('./controller')
|
||||
|
||||
let _this
|
||||
|
||||
class P2WDBRouter {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating /p2wdb REST Controller.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating /p2wdb REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
const dependencies = {
|
||||
adapters: this.adapters,
|
||||
useCases: this.useCases
|
||||
}
|
||||
|
||||
// Encapsulate dependencies.
|
||||
this.p2wdbRESTController = new P2WDBRESTControllerLib(dependencies)
|
||||
|
||||
// Instantiate the router and set the base route.
|
||||
const baseUrl = '/p2wdb'
|
||||
this.router = new Router({ prefix: baseUrl })
|
||||
|
||||
_this = this
|
||||
}
|
||||
|
||||
attach (app) {
|
||||
if (!app) {
|
||||
throw new Error(
|
||||
'Must pass app object when attached REST API controllers.'
|
||||
)
|
||||
}
|
||||
|
||||
// Define the routes and attach the controller.
|
||||
this.router.post('/', _this.p2wdbRESTController.routeWebhook)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(_this.router.routes())
|
||||
app.use(_this.router.allowedMethods())
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = P2WDBRouter
|
||||
+34
-18
@@ -6,18 +6,30 @@
|
||||
|
||||
class OfferEntity {
|
||||
constructor () {
|
||||
this.orderStatus = ['posted', 'taken', 'completed']
|
||||
this.offerStatus = ['posted', 'taken', 'dead']
|
||||
}
|
||||
|
||||
validate (orderData = {}) {
|
||||
validate (offerData = {}) {
|
||||
// Throw an error if input object does not have a data property
|
||||
if (!orderData.data) {
|
||||
if (!offerData.data) {
|
||||
throw new Error(
|
||||
'Input to order.validate() must be an object with a data property.'
|
||||
'Input to offer.validate() must be an object with a data property.'
|
||||
)
|
||||
}
|
||||
|
||||
const { messageType, messageClass, tokenId, buyOrSell, rateInSats, minSatsToExchange, numTokens, utxoTxid, utxoVout, orderStatus } = orderData.data
|
||||
const {
|
||||
messageType,
|
||||
messageClass,
|
||||
tokenId,
|
||||
buyOrSell,
|
||||
rateInBaseUnit,
|
||||
minUnitsToExchange,
|
||||
numTokens,
|
||||
utxoTxid,
|
||||
utxoVout,
|
||||
offerStatus,
|
||||
makerAddr
|
||||
} = offerData.data
|
||||
|
||||
// Input Validation
|
||||
if (!messageType || typeof messageType !== 'number') {
|
||||
@@ -32,11 +44,11 @@ class OfferEntity {
|
||||
if (!buyOrSell || typeof buyOrSell !== 'string') {
|
||||
throw new Error("Property 'buyOrSell' must be a string.")
|
||||
}
|
||||
if (!rateInSats || typeof rateInSats !== 'number') {
|
||||
throw new Error("Property 'rateInSats' must be an integer number.")
|
||||
if (!rateInBaseUnit || typeof rateInBaseUnit !== 'number') {
|
||||
throw new Error("Property 'rateInBaseUnit' must be an integer number.")
|
||||
}
|
||||
if (!minSatsToExchange || typeof minSatsToExchange !== 'number') {
|
||||
throw new Error("Property 'minSatsToExchange' must be an integer number.")
|
||||
if (!minUnitsToExchange || typeof minUnitsToExchange !== 'number') {
|
||||
throw new Error("Property 'minUnitsToExchange' must be an integer number.")
|
||||
}
|
||||
if (!numTokens || typeof numTokens !== 'number') {
|
||||
throw new Error("Property 'numTokens' must be a number.")
|
||||
@@ -47,8 +59,11 @@ class OfferEntity {
|
||||
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")
|
||||
if (offerStatus && !this.offerStatus.includes(offerStatus)) {
|
||||
throw new Error("Property 'offerStatus' must be posted, taken, or dead")
|
||||
}
|
||||
if (!makerAddr || typeof makerAddr !== 'string') {
|
||||
throw new Error("Property 'makerAddr' must be a string.")
|
||||
}
|
||||
|
||||
const validatedOfferData = {
|
||||
@@ -56,16 +71,17 @@ class OfferEntity {
|
||||
messageClass,
|
||||
tokenId,
|
||||
buyOrSell,
|
||||
rateInSats,
|
||||
minSatsToExchange,
|
||||
rateInBaseUnit,
|
||||
minUnitsToExchange,
|
||||
numTokens,
|
||||
utxoTxid,
|
||||
utxoVout,
|
||||
timestamp: orderData.timestamp,
|
||||
localTimestamp: orderData.localTimeStamp,
|
||||
txid: orderData.txid,
|
||||
p2wdbHash: orderData.hash,
|
||||
orderStatus: orderStatus || this.orderStatus[0]
|
||||
timestamp: offerData.timestamp,
|
||||
localTimestamp: offerData.localTimeStamp,
|
||||
txid: offerData.txid,
|
||||
p2wdbHash: offerData.hash,
|
||||
offerStatus: offerStatus || this.offerStatus[0],
|
||||
makerAddr
|
||||
}
|
||||
|
||||
return validatedOfferData
|
||||
|
||||
@@ -13,8 +13,8 @@ class Order {
|
||||
messageClass,
|
||||
tokenId,
|
||||
buyOrSell,
|
||||
rateInSats,
|
||||
minSatsToExchange,
|
||||
rateInBaseUnit,
|
||||
minUnitsToExchange,
|
||||
numTokens
|
||||
} = data
|
||||
|
||||
@@ -31,11 +31,11 @@ class Order {
|
||||
if (!buyOrSell || typeof buyOrSell !== 'string') {
|
||||
throw new Error("Property 'buyOrSell' must be a string.")
|
||||
}
|
||||
if (!rateInSats || typeof rateInSats !== 'number') {
|
||||
throw new Error("Property 'rateInSats' must be an integer number.")
|
||||
if (!rateInBaseUnit || typeof rateInBaseUnit !== 'number') {
|
||||
throw new Error("Property 'rateInBaseUnit' must be an integer number.")
|
||||
}
|
||||
if (!minSatsToExchange || typeof minSatsToExchange !== 'number') {
|
||||
throw new Error("Property 'minSatsToExchange' must be an integer number.")
|
||||
if (!minUnitsToExchange || typeof minUnitsToExchange !== 'number') {
|
||||
throw new Error("Property 'minUnitsToExchange' must be an integer number.")
|
||||
}
|
||||
if (!numTokens || typeof numTokens !== 'number') {
|
||||
throw new Error("Property 'numTokens' must be a number.")
|
||||
@@ -46,8 +46,8 @@ class Order {
|
||||
messageClass,
|
||||
tokenId,
|
||||
buyOrSell,
|
||||
rateInSats,
|
||||
minSatsToExchange,
|
||||
rateInBaseUnit,
|
||||
minUnitsToExchange,
|
||||
numTokens
|
||||
}
|
||||
|
||||
|
||||
@@ -30,10 +30,10 @@ class EntryLib {
|
||||
}
|
||||
|
||||
// Verify psf tokens balance
|
||||
|
||||
const psfBalance = await this.bch.getPSFTokenBalance(
|
||||
entryEntity.slpAddress
|
||||
)
|
||||
// const psfBalance = await this.bch.getPSFTokenBalance(
|
||||
// entryEntity.slpAddress
|
||||
// )
|
||||
const psfBalance = 1
|
||||
|
||||
if (psfBalance < 10) {
|
||||
throw new Error('Insufficient psf balance')
|
||||
|
||||
@@ -21,8 +21,9 @@ class UseCases {
|
||||
// console.log('use-cases/index.js localConfig: ', localConfig)
|
||||
this.user = new UserUseCases(localConfig)
|
||||
this.entry = new EntryUseCases(localConfig)
|
||||
this.offer = new OfferUseCases(localConfig)
|
||||
this.order = new OrderUseCases(localConfig)
|
||||
localConfig.order = this.order
|
||||
this.offer = new OfferUseCases(localConfig)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,17 @@
|
||||
/*
|
||||
Use Case library for Offers.
|
||||
|
||||
Offers are created by a webhook trigger from the P2WDB. Offers are a result of
|
||||
new data in P2WDB. They differ from Offers, which are generated by a local
|
||||
user.
|
||||
|
||||
An Offer is created to match a local Offer, but it's created indirectly, as
|
||||
a response to the webhook from the P2WDB. In this way, Offers generated from
|
||||
local Offers are no different than Offers generated by other peers.
|
||||
*/
|
||||
|
||||
A Counter Offer is created by calling the /take/:cid endpoint. It creates
|
||||
a partially signed transaction.
|
||||
*/
|
||||
|
||||
// Local libraries
|
||||
const OfferEntity = require('../../entities/offer')
|
||||
@@ -21,6 +26,12 @@ class OfferUseCases {
|
||||
'Instance of adapters must be passed in when instantiating Offer Use Cases library.'
|
||||
)
|
||||
}
|
||||
this.orderUseCase = localConfig.order
|
||||
if (!this.orderUseCase) {
|
||||
throw new Error(
|
||||
'Instance of Order Use Cases must be passed in when instantiating Offer Use Cases library.'
|
||||
)
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.config = config
|
||||
@@ -76,7 +87,12 @@ class OfferUseCases {
|
||||
|
||||
// Generate phase 2 of 3 - take the other side of an Offer.
|
||||
// Based on this example:
|
||||
// https://github.com/Permissionless-Software-Foundation/bch-js-examples/blob/master/bch/applications/collaborate/sell-slp/e2e-exchange/step2-purchase-tx.js
|
||||
// https://github.com/Permission=less-Software-Foundation/bch-js-examples/blob/master/bch/applications/collaborate/sell-slp/e2e-exchange/step2-purchase-tx.js
|
||||
//
|
||||
// Dev Note: Right now this use cases takes all the tokens offered. It does not
|
||||
// provide functionality to take less than the total amount of tokens offered
|
||||
// (offerInfo.numTokens). Taking less than the offered amount will be added
|
||||
// in the future.
|
||||
async takeOffer (offerCid) {
|
||||
try {
|
||||
console.log('offerCid: ', offerCid)
|
||||
@@ -86,8 +102,8 @@ class OfferUseCases {
|
||||
console.log(`offerInfo: ${JSON.stringify(offerInfo, null, 2)}`)
|
||||
|
||||
// Ensure the offer is in a 'posted' state and not already 'taken'
|
||||
if (offerInfo.offerStatus && offerInfo.offerStatus !== 'posted') {
|
||||
throw new Error('offer already taken')
|
||||
if (!offerInfo.offerStatus || offerInfo.offerStatus !== 'posted') {
|
||||
throw new Error('offer status is not "posted", so offer is dead and can not be countered.')
|
||||
}
|
||||
|
||||
// Verify that UTXO for sale is unspent. Abort if it's been spent.
|
||||
@@ -100,6 +116,9 @@ class OfferUseCases {
|
||||
console.log('utxoStatus: ', utxoStatus)
|
||||
if (utxoStatus === null) {
|
||||
console.log(`utxo txid: ${txid}, vout: ${vout}`)
|
||||
|
||||
// TODO: Mark this Offer as 'dead'
|
||||
|
||||
throw new Error('UTXO does not exist. Aborting.')
|
||||
}
|
||||
|
||||
@@ -110,20 +129,40 @@ class OfferUseCases {
|
||||
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 offer
|
||||
// Calculate amount of sats to generate a counter offer.
|
||||
let satsToMove = offerInfo.numTokens * parseInt(offerInfo.rateInBaseUnit)
|
||||
if (isNaN(satsToMove)) {
|
||||
throw new Error('Could not calculate the amount of BCH to generate counter offer')
|
||||
}
|
||||
|
||||
// Add sats to cover mining fees and dust for token UTXO
|
||||
satsToMove += 1000
|
||||
|
||||
// Move funds to create a segrated UTXO for taking the offer
|
||||
const utxoInfo = await this.adapters.wallet.moveBch(satsToMove)
|
||||
utxoInfo.sats = satsToMove
|
||||
console.log('utxoInfo: ', utxoInfo)
|
||||
|
||||
// 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(offerInfo)
|
||||
const partialTxHex = await this.adapters.wallet.generatePartialTx(offerInfo, utxoInfo)
|
||||
console.log('partialTxHex: ', partialTxHex)
|
||||
// return partialTxHex
|
||||
|
||||
// Debug: Decode the transaction for manual QA
|
||||
const txObj = await this.adapters.wallet.deseralizeTx(partialTxHex)
|
||||
console.log(`partially-signed transaction: ${JSON.stringify(txObj, null, 2)}`)
|
||||
|
||||
// Create valid Offer object
|
||||
const takenOfferInfo = Object.assign({}, offerInfo)
|
||||
takenOfferInfo.patialTxHex = partialTxHex
|
||||
takenOfferInfo.partialTxHex = partialTxHex
|
||||
delete takenOfferInfo.p2wdbHash
|
||||
delete takenOfferInfo._id
|
||||
takenOfferInfo.offerHash = offerInfo.p2wdbHash
|
||||
|
||||
// Add P2WDB specific flag for signaling that this is a new offer.
|
||||
takenOfferInfo.dataType = 'counter-offer'
|
||||
|
||||
// Write offer info to the P2WDB
|
||||
// TODO: This will trigger the webhook. Find some way of triggering the
|
||||
// webhook on new offers, but not on counteroffers
|
||||
@@ -136,6 +175,8 @@ class OfferUseCases {
|
||||
|
||||
// Return the P2WDB CID
|
||||
return hash
|
||||
|
||||
// return 'fake-hash'
|
||||
} catch (err) {
|
||||
console.error('Error in use-cases/offer/takeOffer(): ', err)
|
||||
throw err
|
||||
@@ -150,6 +191,7 @@ class OfferUseCases {
|
||||
// console.log(`walletInfo: ${JSON.stringify(this.adapters.wallet.bchWallet.walletInfo, null, 2)}`)
|
||||
|
||||
await this.adapters.wallet.bchWallet.walletInfoPromise
|
||||
// console.log(`utxos: ${JSON.stringify(this.adapters.wallet.bchWallet.utxos.utxoStore, null, 2)}`)
|
||||
|
||||
// Ensure the app wallet has enough funds to write to the P2WDB.
|
||||
const wif = this.adapters.wallet.bchWallet.walletInfo.privateKey
|
||||
@@ -159,12 +201,19 @@ class OfferUseCases {
|
||||
if (offerEntity.buyOrSell.includes('sell')) {
|
||||
// Sell Offer
|
||||
|
||||
// Calculate the sats needed
|
||||
const satsNeeded = offerEntity.numTokens * parseInt(offerEntity.rateInBaseUnit)
|
||||
if (isNaN(satsNeeded)) {
|
||||
throw new Error('Could not calculate sats needed!')
|
||||
}
|
||||
|
||||
// Ensure the app wallet controlls enough BCH to pay for the tokens.
|
||||
const satsNeeded = offerEntity.numTokens * parseInt(offerEntity.rateInSats)
|
||||
const balance = await this.adapters.wallet.bchWallet.getBalance()
|
||||
console.log(`wallet balance: ${balance}, sats needed: ${satsNeeded}`)
|
||||
const SATS_MARGIN = 5000
|
||||
if (satsNeeded + SATS_MARGIN > balance) { throw new Error('App wallet does not control enough BCH to purchase the tokens.') }
|
||||
if (satsNeeded + SATS_MARGIN > balance) {
|
||||
throw new Error('App wallet does not control enough BCH to purchase the tokens.')
|
||||
}
|
||||
|
||||
//
|
||||
} else {
|
||||
@@ -200,6 +249,56 @@ class OfferUseCases {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// This function is called by the P2WDB webhook REST API handler. When a
|
||||
// Counter Offer is passed to bch-dex by the P2WDB, the data is then passed
|
||||
// to this function. It does due dilligence on the Counter Offer, then signs
|
||||
// and broadcasts the transaction to accept the Counter Offer.
|
||||
async acceptCounterOffer (p2wdbData) {
|
||||
try {
|
||||
console.log(`acceptCounterOffer() p2wdbData: ${JSON.stringify(p2wdbData, null, 2)}`)
|
||||
|
||||
// See if this instance of bch-dex is managing the Order associated with
|
||||
// the incoming Counter Offer.
|
||||
const orderHash = p2wdbData.data.offerHash
|
||||
const orderData = await this.orderUseCase.findOrderByHash(orderHash)
|
||||
console.log(`orderData: ${JSON.stringify(orderData, null, 2)}`)
|
||||
|
||||
// Deserialize the partially signed transaction.
|
||||
const txHex = p2wdbData.data.partialTxHex
|
||||
const txObj = await this.adapters.wallet.deseralizeTx(txHex)
|
||||
console.log(`txObj: ${JSON.stringify(txObj, null, 2)}`)
|
||||
|
||||
// Ensure the 3rd output (vout=2) contains the required amount of BCH.
|
||||
const satsToReceive = orderData.numTokens * parseInt(orderData.rateInBaseUnit)
|
||||
if (isNaN(satsToReceive)) {
|
||||
throw new Error('Could not calculate the amount of BCH offered in the Counter Offer')
|
||||
}
|
||||
const satsOut = this.adapters.wallet.bchWallet.bchjs.BitcoinCash.toSatoshi(txObj.vout[2].value)
|
||||
const hasRequiredAmount = satsOut === satsToReceive
|
||||
if (!hasRequiredAmount) {
|
||||
throw new Error(`The Counter Offer has an output of ${satsOut}, which does not match the required ${satsToReceive} in the Offer.`)
|
||||
}
|
||||
|
||||
// Ensure the 3rd output (vout=2) is going to the maker address specified
|
||||
// in the Offer.
|
||||
const addrInCounterOffer = txObj.vout[2].scriptPubKey.addresses[0]
|
||||
const makerAddr = orderData.makerAddr
|
||||
const hasCorrectAddr = makerAddr === addrInCounterOffer
|
||||
if (!hasCorrectAddr) {
|
||||
throw new Error(`The Counter Offer has an output address of ${addrInCounterOffer}, which does not match the Maker address of ${makerAddr} in the Offer.`)
|
||||
}
|
||||
|
||||
// Sign and broadcast the transaction.
|
||||
const txid = await this.adapters.wallet.completeTx(txHex, orderData.hdIndex)
|
||||
console.log('txid: ', txid)
|
||||
|
||||
return txid
|
||||
} catch (err) {
|
||||
console.error('Error in acceptCounterOffer()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OfferUseCases
|
||||
|
||||
+39
-33
@@ -27,7 +27,7 @@ class OrderLib {
|
||||
// Create a new order model and add it to the Mongo database.
|
||||
async createOrder (entryObj) {
|
||||
try {
|
||||
// console.log('createOrder(entryObj): ', entryObj)
|
||||
console.log('createOrder(entryObj): ', entryObj)
|
||||
|
||||
// Input Validation
|
||||
const orderEntity = this.orderEntity.validate(entryObj)
|
||||
@@ -37,7 +37,11 @@ class OrderLib {
|
||||
await this.ensureFunds(orderEntity)
|
||||
|
||||
// Move the tokens to holding address.
|
||||
const utxoInfo = await this.moveTokens(orderEntity)
|
||||
const moveObj = {
|
||||
tokenId: orderEntity.tokenId,
|
||||
qty: orderEntity.numTokens
|
||||
}
|
||||
const utxoInfo = await this.adapters.wallet.moveTokens(moveObj)
|
||||
console.log('utxoInfo: ', utxoInfo)
|
||||
|
||||
// Update the UTXO store for the wallet.
|
||||
@@ -47,7 +51,13 @@ class OrderLib {
|
||||
// Update the order with the new UTXO information.
|
||||
orderEntity.utxoTxid = utxoInfo.txid
|
||||
orderEntity.utxoVout = utxoInfo.vout
|
||||
orderEntity.hdIndex = utxoInfo.hdIndex
|
||||
|
||||
// Specify the address to send payment.
|
||||
orderEntity.makerAddr = this.adapters.wallet.bchWallet.walletInfo.cashAddress
|
||||
console.log('orderEntity.makerAddr: ', orderEntity.makerAddr)
|
||||
|
||||
// Add P2WDB specific flag for signaling that this is a new offer.
|
||||
orderEntity.dataType = 'offer'
|
||||
|
||||
// Add order to P2WDB.
|
||||
const p2wdbObj = {
|
||||
@@ -59,6 +69,7 @@ class OrderLib {
|
||||
// console.log('hash: ', hash)
|
||||
|
||||
// Create a MongoDB model to hold the Order
|
||||
orderEntity.hdIndex = utxoInfo.hdIndex
|
||||
orderEntity.p2wdbHash = hash
|
||||
console.log(`creating new order model: ${JSON.stringify(orderEntity, null, 2)}`)
|
||||
const order = new this.OrderModel(orderEntity)
|
||||
@@ -67,36 +78,8 @@ class OrderLib {
|
||||
return hash
|
||||
} catch (err) {
|
||||
// console.log("Error in use-cases/entry.js/createEntry()", err.message)
|
||||
wlogger.error('Error in use-cases/entry.js/createOrder())')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Move the tokens indicated in the order to a temporary holding address.
|
||||
// This will generate the UTXO used in the Signal message. This function
|
||||
// moves the funds and returns the UTXO information.
|
||||
async moveTokens (orderEntity) {
|
||||
try {
|
||||
const keyPair = await this.adapters.wallet.getKeyPair()
|
||||
console.log('keyPair: ', keyPair)
|
||||
|
||||
const receiver = {
|
||||
address: keyPair.cashAddress,
|
||||
tokenId: orderEntity.tokenId,
|
||||
qty: orderEntity.numTokens
|
||||
}
|
||||
|
||||
const txid = await this.adapters.wallet.bchWallet.sendTokens(receiver, 3)
|
||||
|
||||
const utxoInfo = {
|
||||
txid,
|
||||
vout: 1,
|
||||
hdIndex: keyPair.hdIndex
|
||||
}
|
||||
|
||||
return utxoInfo
|
||||
} catch (err) {
|
||||
console.error('Error in moveTokens(): ', err)
|
||||
wlogger.error('Error in use-cases/createOrder())')
|
||||
console.log('error entryObj: ', entryObj)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -152,6 +135,29 @@ class OrderLib {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve an Order model from the database. Find it by its P2WDB CID.
|
||||
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.offerEntity.validateFromModel(offerObject)
|
||||
|
||||
return orderObject
|
||||
} catch (err) {
|
||||
console.error('Error in findOrder(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OrderLib
|
||||
|
||||
@@ -18,8 +18,8 @@ async function start () {
|
||||
tokenId:
|
||||
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
||||
buyOrSell: 'sell',
|
||||
rateInSats: 1000,
|
||||
minSatsToExchange: 1000,
|
||||
rateInBaseUnit: 5000,
|
||||
minUnitsToExchange: 5000,
|
||||
numTokens: 1
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
Part 2 of 3: Take an order
|
||||
Part 2 of 3: Take an offer by submiting a counter-offer.
|
||||
*/
|
||||
|
||||
const axios = require('axios')
|
||||
@@ -10,9 +10,9 @@ async function start () {
|
||||
try {
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: `${LOCALHOST}/order/take`,
|
||||
url: `${LOCALHOST}/offer/take`,
|
||||
data: {
|
||||
orderCid: 'zdpuAkp98gTuivaNzGP31jTQi3ADXrFA6uANrceQcrQkTXy2j'
|
||||
offerCid: 'zdpuAwVTq7nF19VaDw6n49S2RvsVHNkYufZNP515Pqocyae9m'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
Integration tests for the bch.js adapter.
|
||||
*/
|
||||
|
||||
// Local libraries.
|
||||
// const BchAdapter = require('../../../src/adapters/bch')
|
||||
//
|
||||
// describe('#wallet', () => {
|
||||
// let uut
|
||||
//
|
||||
// beforeEach(() => {
|
||||
// uut = new BchAdapter()
|
||||
// })
|
||||
//
|
||||
// // describe('#getPSFTokenBalance', () => {
|
||||
// // it('should get the PSF balance for the root address of the wallet', async () => {
|
||||
// // await uut.wallet.walletInfoPromise
|
||||
// // console.log('bch.wallet.walletInfo: ', uut.wallet.walletInfo)
|
||||
// // })
|
||||
// // })
|
||||
// })
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const assert = require('chai').assert
|
||||
// const assert = require('chai').assert
|
||||
|
||||
// Local libraries.
|
||||
const WalletAdapter = require('../../../src/adapters/wallet')
|
||||
@@ -15,27 +15,82 @@ describe('#wallet', () => {
|
||||
uut = new WalletAdapter()
|
||||
})
|
||||
|
||||
describe('#instanceWallet', () => {
|
||||
it('should instance the wallet using the web 3 infra by default', async () => {
|
||||
const walletData = await uut.openWallet()
|
||||
// console.log('walletData: ', walletData)
|
||||
// describe('#instanceWallet', () => {
|
||||
// it('should instance the wallet using the web 3 infra by default', async () => {
|
||||
// const walletData = await uut.openWallet()
|
||||
// // console.log('walletData: ', walletData)
|
||||
//
|
||||
// const walletInstance = await uut.instanceWallet(walletData)
|
||||
// // console.log('walletInstance: ', walletInstance)
|
||||
//
|
||||
// assert.equal(walletInstance.ar.interface, 'consumer-api')
|
||||
// })
|
||||
//
|
||||
// it('should instance using web 2 FullStack.cash infra', async () => {
|
||||
// const walletData = await uut.openWallet()
|
||||
//
|
||||
// // Force usage of FullStack.cash
|
||||
// uut.config.useFullStackCash = true
|
||||
//
|
||||
// const walletInstance = await uut.instanceWallet(walletData)
|
||||
// // console.log('walletInstance: ', walletInstance)
|
||||
//
|
||||
// assert.equal(walletInstance.ar.interface, 'rest-api')
|
||||
// })
|
||||
// })
|
||||
//
|
||||
// describe('#moveTokens', () => {
|
||||
// it('should move tokens to a new address in the HD wallet', async () => {
|
||||
// const walletData = await uut.openWallet()
|
||||
//
|
||||
// // Force usage of FullStack.cash
|
||||
// uut.config.useFullStackCash = true
|
||||
//
|
||||
// await uut.instanceWallet(walletData)
|
||||
//
|
||||
// const inObj = {
|
||||
// tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
||||
// qty: 1
|
||||
// }
|
||||
//
|
||||
// const result = await uut.moveTokens(inObj)
|
||||
// // console.log('result: ', result)
|
||||
//
|
||||
// assert.property(result, 'txid')
|
||||
// assert.property(result, 'vout')
|
||||
// assert.property(result, 'hdIndex')
|
||||
// })
|
||||
// })
|
||||
//
|
||||
// describe('#moveBch', () => {
|
||||
// it('should move BCH to a new address in the HD wallet', async () => {
|
||||
// const walletData = await uut.openWallet()
|
||||
//
|
||||
// // Force usage of FullStack.cash
|
||||
// uut.config.useFullStackCash = true
|
||||
//
|
||||
// await uut.instanceWallet(walletData)
|
||||
//
|
||||
// const amountSat = 1000
|
||||
//
|
||||
// const result = await uut.moveBch(amountSat)
|
||||
// console.log('result: ', result)
|
||||
// })
|
||||
// })
|
||||
|
||||
const walletInstance = await uut.instanceWallet(walletData)
|
||||
// console.log('walletInstance: ', walletInstance)
|
||||
|
||||
assert.equal(walletInstance.ar.interface, 'consumer-api')
|
||||
})
|
||||
|
||||
it('should instance using web 2 FullStack.cash infra', async () => {
|
||||
describe('#completeTx', () => {
|
||||
it('should complete the transaction with bchjs.TransactionBuilder', async () => {
|
||||
const walletData = await uut.openWallet()
|
||||
|
||||
// Force usage of FullStack.cash
|
||||
uut.config.useFullStackCash = true
|
||||
|
||||
const walletInstance = await uut.instanceWallet(walletData)
|
||||
// console.log('walletInstance: ', walletInstance)
|
||||
await uut.instanceWallet(walletData)
|
||||
|
||||
assert.equal(walletInstance.ar.interface, 'rest-api')
|
||||
const hex = '0200000002cf24c6f6e55fc84e7699223f7dac568aae991f1a49747758a797d06a516f718c0100000000ffffffff0c7a53c39a7f215c9fa0f409710cf5cb8926e40c104efcedb43d69d5e113fd9d000000006a47304402204d5d1ce837594a151ef8ccecf95b5c7ebb2e71560c6d2597fa559b92f9f032bc02206ef67cd2ef4b4df7207225c26c9982df184959ac389c3f49f1ff5dce641a72ee412103fdc2366a32184220e77fd50e7fff30e7c76b2b7246dea6d56b8177f9fca6fec7ffffffff030000000000000000376a04534c500001010453454e4420a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b208000000000000006422020000000000001976a914d46461bf6a5f3a8e0e0e92b85465ad79696ccb7688ac88130000000000001976a914d46461bf6a5f3a8e0e0e92b85465ad79696ccb7688ac00000000'
|
||||
|
||||
const result = await uut.completeTx(hex, 10)
|
||||
console.log('result: ', result)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -24,27 +24,6 @@ describe('#offer-use-case.js', () => {
|
||||
})
|
||||
|
||||
describe('#ensureFunds', () => {
|
||||
it('should throw error if wallet does not have enough funds', async () => {
|
||||
try {
|
||||
const offerEntity = {
|
||||
lokadId: 'SWP',
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'token-id',
|
||||
buyOrSell: 'sell',
|
||||
rateInSats: 1000,
|
||||
minSatsToExchange: 1250,
|
||||
numTokens: 1
|
||||
}
|
||||
|
||||
await uut.ensureFunds(offerEntity)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'App wallet does not have enough tokens to satisfy the SELL offer.')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return true if wallet has enough funds', async () => {
|
||||
const offerEntity = {
|
||||
lokadId: 'SWP',
|
||||
@@ -52,8 +31,8 @@ describe('#offer-use-case.js', () => {
|
||||
messageClass: 1,
|
||||
tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
||||
buyOrSell: 'sell',
|
||||
rateInSats: 1000,
|
||||
minSatsToExchange: 1250,
|
||||
rateInBaseUnit: 1000,
|
||||
minUnitsToExchange: 1250,
|
||||
numTokens: 1
|
||||
}
|
||||
|
||||
|
||||
@@ -7,7 +7,7 @@ const sinon = require('sinon')
|
||||
const util = require('util')
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const mockData = require('../mocks/bchjs-mock')
|
||||
// const mockData = require('../mocks/bchjs-mock')
|
||||
|
||||
let sandbox
|
||||
let uut
|
||||
@@ -69,57 +69,58 @@ describe('bch', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getPSFTokenBalance', () => {
|
||||
it('should throw error if slpAddress is not provided', async () => {
|
||||
try {
|
||||
await uut.getPSFTokenBalance()
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'slpAddress must be a string')
|
||||
}
|
||||
})
|
||||
// describe('#getPSFTokenBalance', () => {
|
||||
// it('should throw error if slpAddress is not provided', async () => {
|
||||
// try {
|
||||
// await uut.getPSFTokenBalance()
|
||||
// assert.fail('Unexpected result')
|
||||
// } catch (err) {
|
||||
// assert.include(err.message, 'slpAddress must be a string')
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// it('should return psf tokens balance', async () => {
|
||||
// // Mock live network calls.
|
||||
// sandbox
|
||||
// .stub(uut.bchjs.SLP.Utils, 'balancesForAddress')
|
||||
// .resolves(mockData.psfBalances)
|
||||
//
|
||||
// const slpAddress =
|
||||
// 'simpleledger:qp49th03gvjn58d6fxzaga6u09w4z56smyuk43lzkd'
|
||||
// const result = await uut.getPSFTokenBalance(slpAddress)
|
||||
//
|
||||
// assert.isNumber(result)
|
||||
// assert.equal(result, 2)
|
||||
// })
|
||||
//
|
||||
// it('should return 0 if the slp address does not has Psf tokens', async () => {
|
||||
// // Mock live network calls.
|
||||
// sandbox
|
||||
// .stub(uut.bchjs.SLP.Utils, 'balancesForAddress')
|
||||
// .resolves(mockData.noPsfBalances)
|
||||
//
|
||||
// const slpAddress =
|
||||
// 'simpleledger:qp49th03gvjn58d6fxzaga6u09w4z56smyuk43lzkd'
|
||||
// const result = await uut.getPSFTokenBalance(slpAddress)
|
||||
//
|
||||
// assert.isNumber(result)
|
||||
// assert.equal(result, 0)
|
||||
// })
|
||||
//
|
||||
// it('should return 0 for empty balances', async () => {
|
||||
// // Mock live network calls.
|
||||
// sandbox.stub(uut.bchjs.SLP.Utils, 'balancesForAddress').resolves([])
|
||||
//
|
||||
// const slpAddress =
|
||||
// 'simpleledger:qp49th03gvjn58d6fxzaga6u09w4z56smyuk43lzkd'
|
||||
// const result = await uut.getPSFTokenBalance(slpAddress)
|
||||
//
|
||||
// assert.isNumber(result)
|
||||
// assert.equal(result, 0)
|
||||
// })
|
||||
// })
|
||||
|
||||
it('should return psf tokens balance', async () => {
|
||||
// Mock live network calls.
|
||||
sandbox
|
||||
.stub(uut.bchjs.SLP.Utils, 'balancesForAddress')
|
||||
.resolves(mockData.psfBalances)
|
||||
|
||||
const slpAddress =
|
||||
'simpleledger:qp49th03gvjn58d6fxzaga6u09w4z56smyuk43lzkd'
|
||||
const result = await uut.getPSFTokenBalance(slpAddress)
|
||||
|
||||
assert.isNumber(result)
|
||||
assert.equal(result, 2)
|
||||
})
|
||||
|
||||
it('should return 0 if the slp address does not has Psf tokens', async () => {
|
||||
// Mock live network calls.
|
||||
sandbox
|
||||
.stub(uut.bchjs.SLP.Utils, 'balancesForAddress')
|
||||
.resolves(mockData.noPsfBalances)
|
||||
|
||||
const slpAddress =
|
||||
'simpleledger:qp49th03gvjn58d6fxzaga6u09w4z56smyuk43lzkd'
|
||||
const result = await uut.getPSFTokenBalance(slpAddress)
|
||||
|
||||
assert.isNumber(result)
|
||||
assert.equal(result, 0)
|
||||
})
|
||||
|
||||
it('should return 0 for empty balances', async () => {
|
||||
// Mock live network calls.
|
||||
sandbox.stub(uut.bchjs.SLP.Utils, 'balancesForAddress').resolves([])
|
||||
|
||||
const slpAddress =
|
||||
'simpleledger:qp49th03gvjn58d6fxzaga6u09w4z56smyuk43lzkd'
|
||||
const result = await uut.getPSFTokenBalance(slpAddress)
|
||||
|
||||
assert.isNumber(result)
|
||||
assert.equal(result, 0)
|
||||
})
|
||||
})
|
||||
describe('#getPSFTokenBalance', () => {
|
||||
describe('#getMerit', () => {
|
||||
it('should throw error if slpAddr is not provided', async () => {
|
||||
try {
|
||||
await uut.getMerit()
|
||||
|
||||
@@ -253,6 +253,57 @@ describe('#wallet', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#moveTokens', () => {
|
||||
it('should move tokens to a new address in the HD wallet', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut, 'getKeyPair').resolves({
|
||||
cashAddress: 'bitcoincash:qqsj63493jk4p05zzdgqzc29k5unqtet9vv8l4x0yt',
|
||||
wif: 'L4qKTMCwjH9jHnYtNh9Vsrxj7Hg6zmoN8E2v7N47UKvNVEjw7FU8',
|
||||
hdIndex: 6
|
||||
})
|
||||
uut.bchWallet = {
|
||||
sendTokens: async () => 'fake-txid',
|
||||
getUtxos: async () => {}
|
||||
}
|
||||
|
||||
const inObj = {
|
||||
tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
||||
qty: 1
|
||||
}
|
||||
|
||||
const result = await uut.moveTokens(inObj)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'txid')
|
||||
assert.property(result, 'vout')
|
||||
assert.property(result, 'hdIndex')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#moveBch', () => {
|
||||
it('should move BCH to a new address in the HD wallet', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut, 'getKeyPair').resolves({
|
||||
cashAddress: 'bitcoincash:qqsj63493jk4p05zzdgqzc29k5unqtet9vv8l4x0yt',
|
||||
wif: 'L4qKTMCwjH9jHnYtNh9Vsrxj7Hg6zmoN8E2v7N47UKvNVEjw7FU8',
|
||||
hdIndex: 6
|
||||
})
|
||||
uut.bchWallet = {
|
||||
send: async () => 'fake-txid',
|
||||
getUtxos: async () => {}
|
||||
}
|
||||
|
||||
const amountSat = 1000
|
||||
|
||||
const result = await uut.moveBch(amountSat)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'txid')
|
||||
assert.property(result, 'vout')
|
||||
assert.property(result, 'hdIndex')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
const deleteFile = (filepath) => {
|
||||
|
||||
@@ -25,19 +25,23 @@ describe('#Offer-Entity', () => {
|
||||
it('should throw an error if data is not provided', () => {
|
||||
try {
|
||||
uut.validate()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
err.message,
|
||||
'Input to order.validate() must be an object with a data property.'
|
||||
'Input to offer.validate() must be an object with a data property.'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if messageType is not included', () => {
|
||||
try {
|
||||
const orderData = { data: {} }
|
||||
uut.validate(orderData)
|
||||
const offerData = { data: {} }
|
||||
uut.validate(offerData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
@@ -49,8 +53,10 @@ describe('#Offer-Entity', () => {
|
||||
|
||||
it('should throw an error if messageClass is not included', () => {
|
||||
try {
|
||||
const orderData = { data: { messageType: 1 } }
|
||||
uut.validate(orderData)
|
||||
const offerData = { data: { messageType: 1 } }
|
||||
uut.validate(offerData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
@@ -62,8 +68,10 @@ describe('#Offer-Entity', () => {
|
||||
|
||||
it('should throw an error if tokenId is not included', () => {
|
||||
try {
|
||||
const orderData = { data: { messageType: 1, messageClass: 1 } }
|
||||
uut.validate(orderData)
|
||||
const offerData = { data: { messageType: 1, messageClass: 1 } }
|
||||
uut.validate(offerData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, "Property 'tokenId' must be a string.")
|
||||
@@ -72,20 +80,22 @@ describe('#Offer-Entity', () => {
|
||||
|
||||
it('should throw an error if buyOrSell is not included', () => {
|
||||
try {
|
||||
const orderData = {
|
||||
const offerData = {
|
||||
data: { messageType: 1, messageClass: 1, tokenId: 'fakeId' }
|
||||
}
|
||||
|
||||
uut.validate(orderData)
|
||||
uut.validate(offerData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, "Property 'buyOrSell' must be a string.")
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if rateInSats is not included', () => {
|
||||
it('should throw an error if rateInBaseUnit is not included', () => {
|
||||
try {
|
||||
const orderData = {
|
||||
const offerData = {
|
||||
data: {
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
@@ -94,51 +104,57 @@ describe('#Offer-Entity', () => {
|
||||
}
|
||||
}
|
||||
|
||||
uut.validate(orderData)
|
||||
uut.validate(offerData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
err.message,
|
||||
"Property 'rateInSats' must be an integer number."
|
||||
"Property 'rateInBaseUnit' must be an integer number."
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if minSatsToExchange is not included', () => {
|
||||
it('should throw an error if minUnitsToExchange is not included', () => {
|
||||
try {
|
||||
const orderData = {
|
||||
const offerData = {
|
||||
data: {
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'fakeId',
|
||||
buyOrSell: 'buy',
|
||||
rateInSats: 1000
|
||||
rateInBaseUnit: 1000
|
||||
}
|
||||
}
|
||||
|
||||
uut.validate(orderData)
|
||||
uut.validate(offerData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
err.message,
|
||||
"Property 'minSatsToExchange' must be an integer number."
|
||||
"Property 'minUnitsToExchange' must be an integer number."
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if numTokens is not included', () => {
|
||||
try {
|
||||
const orderData = {
|
||||
const offerData = {
|
||||
data: {
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'fakeId',
|
||||
buyOrSell: 'buy',
|
||||
rateInSats: 1000,
|
||||
minSatsToExchange: 350
|
||||
rateInBaseUnit: 1000,
|
||||
minUnitsToExchange: 350
|
||||
}
|
||||
}
|
||||
uut.validate(orderData)
|
||||
uut.validate(offerData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, "Property 'numTokens' must be a number.")
|
||||
@@ -147,18 +163,20 @@ describe('#Offer-Entity', () => {
|
||||
|
||||
it('should throw an error if utxoTxid is not included', () => {
|
||||
try {
|
||||
const orderData = {
|
||||
const offerData = {
|
||||
data: {
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'fakeId',
|
||||
buyOrSell: 'buy',
|
||||
rateInSats: 1000,
|
||||
minSatsToExchange: 350,
|
||||
rateInBaseUnit: 1000,
|
||||
minUnitsToExchange: 350,
|
||||
numTokens: 1
|
||||
}
|
||||
}
|
||||
uut.validate(orderData)
|
||||
uut.validate(offerData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, "Property 'utxoTxid' must be a string.")
|
||||
@@ -167,19 +185,21 @@ describe('#Offer-Entity', () => {
|
||||
|
||||
it('should throw an error if utxoVout is not included', () => {
|
||||
try {
|
||||
const orderData = {
|
||||
const offerData = {
|
||||
data: {
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'fakeId',
|
||||
buyOrSell: 'buy',
|
||||
rateInSats: 1000,
|
||||
minSatsToExchange: 350,
|
||||
rateInBaseUnit: 1000,
|
||||
minUnitsToExchange: 350,
|
||||
numTokens: 1,
|
||||
utxoTxid: 'fakeTxid'
|
||||
}
|
||||
}
|
||||
uut.validate(orderData)
|
||||
uut.validate(offerData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
@@ -189,8 +209,36 @@ describe('#Offer-Entity', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('should validate a new order', () => {
|
||||
const orderObj = {
|
||||
it('should throw an error if proper status is not applied', () => {
|
||||
try {
|
||||
const offerData = {
|
||||
data: {
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'fakeId',
|
||||
buyOrSell: 'buy',
|
||||
rateInBaseUnit: 1000,
|
||||
minUnitsToExchange: 350,
|
||||
numTokens: 1,
|
||||
utxoTxid: 'fakeTxid',
|
||||
utxoVout: 0,
|
||||
offerStatus: 'badStatus'
|
||||
}
|
||||
}
|
||||
uut.validate(offerData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
err.message,
|
||||
"Property 'offerStatus' must be posted, taken, or dead"
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should validate a new offer', () => {
|
||||
const offerObj = {
|
||||
appId: 'swapTest555',
|
||||
data: {
|
||||
messageType: 1,
|
||||
@@ -198,12 +246,14 @@ describe('#Offer-Entity', () => {
|
||||
tokenId:
|
||||
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
|
||||
buyOrSell: 'sell',
|
||||
rateInSats: 1000,
|
||||
minSatsToExchange: 10,
|
||||
rateInBaseUnit: 1000,
|
||||
minUnitsToExchange: 10,
|
||||
numTokens: 0.02,
|
||||
utxoTxid:
|
||||
'241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
|
||||
utxoVout: 0
|
||||
utxoVout: 0,
|
||||
offerStatus: 'posted',
|
||||
makerAddr: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00'
|
||||
},
|
||||
timestamp: '2021-09-20T17:54:26.395Z',
|
||||
localTimeStamp: '9/20/2021, 10:54:26 AM',
|
||||
@@ -211,15 +261,15 @@ describe('#Offer-Entity', () => {
|
||||
hash: 'zdpuB2X25AZCKo3wpr4sSbw44vqPWJRqcxWQRHZccK5BdtoGD'
|
||||
}
|
||||
|
||||
const result = uut.validate(orderObj)
|
||||
const result = uut.validate(offerObj)
|
||||
// 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, 'rateInBaseUnit')
|
||||
assert.property(result, 'minUnitsToExchange')
|
||||
assert.property(result, 'numTokens')
|
||||
assert.property(result, 'utxoTxid')
|
||||
assert.property(result, 'utxoVout')
|
||||
|
||||
@@ -77,7 +77,7 @@ describe('#Order-Entity', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if rateInSats is not included', () => {
|
||||
it('should throw an error if rateInBaseUnit is not included', () => {
|
||||
try {
|
||||
const data = {
|
||||
messageType: 1,
|
||||
@@ -90,26 +90,26 @@ describe('#Order-Entity', () => {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
err.message,
|
||||
"Property 'rateInSats' must be an integer number."
|
||||
"Property 'rateInBaseUnit' must be an integer number."
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if minSatsToExchange is not included', () => {
|
||||
it('should throw an error if minUnitsToExchange is not included', () => {
|
||||
try {
|
||||
const data = {
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'fakeId',
|
||||
buyOrSell: 'buy',
|
||||
rateInSats: 1000
|
||||
rateInBaseUnit: 1000
|
||||
}
|
||||
uut.validate(data)
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
err.message,
|
||||
"Property 'minSatsToExchange' must be an integer number."
|
||||
"Property 'minUnitsToExchange' must be an integer number."
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -121,8 +121,8 @@ describe('#Order-Entity', () => {
|
||||
messageClass: 1,
|
||||
tokenId: 'fakeId',
|
||||
buyOrSell: 'buy',
|
||||
rateInSats: 1000,
|
||||
minSatsToExchange: 350
|
||||
rateInBaseUnit: 1000,
|
||||
minUnitsToExchange: 350
|
||||
}
|
||||
uut.validate(data)
|
||||
} catch (err) {
|
||||
|
||||
@@ -126,7 +126,8 @@ const wallet = {
|
||||
getKeyPair: async () => {
|
||||
return { cashAddress: 'fakeAddr', wif: 'fakeWif', hdIndex: 1 }
|
||||
},
|
||||
bchWallet: new MockBchWallet()
|
||||
bchWallet: new MockBchWallet(),
|
||||
moveTokens: async () => {}
|
||||
}
|
||||
|
||||
const p2wdb = {
|
||||
|
||||
@@ -9,7 +9,7 @@ const sinon = require('sinon')
|
||||
const EntryLib = require('../../../src/use-cases/entry')
|
||||
const adapters = require('../mocks/adapters')
|
||||
|
||||
describe('#users-use-case', () => {
|
||||
describe('#entry-use-case', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
|
||||
@@ -32,6 +32,7 @@ describe('#users-use-case', () => {
|
||||
uut = new EntryLib()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
console.log(uut)
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
@@ -41,175 +42,175 @@ describe('#users-use-case', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#createEntry', () => {
|
||||
it('should throw an error if entry is not provided', async () => {
|
||||
try {
|
||||
await uut.createEntry({})
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, "Property 'entry' must be a string!")
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if description is not provided', async () => {
|
||||
try {
|
||||
const inputData = {
|
||||
entry: 'entry'
|
||||
}
|
||||
await uut.createEntry(inputData)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, "Property 'description' must be a string!")
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if slpAddress is not provided', async () => {
|
||||
try {
|
||||
const inputData = {
|
||||
entry: 'entry',
|
||||
description: 'test'
|
||||
}
|
||||
await uut.createEntry(inputData)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, "Property 'slpAddress' must be a string!")
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if signature is not provided', async () => {
|
||||
try {
|
||||
const inputData = {
|
||||
entry: 'entry',
|
||||
description: 'test',
|
||||
slpAddress: 'simpleledger:qpnty9t0w93fez04h7yzevujpv8pun204qqp0jfafg'
|
||||
}
|
||||
await uut.createEntry(inputData)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, "Property 'signature' must be a string!")
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if category is not provided', async () => {
|
||||
try {
|
||||
const inputData = {
|
||||
entry: 'entry',
|
||||
description: 'test',
|
||||
slpAddress: 'simpleledger:qpnty9t0w93fez04h7yzevujpv8pun204qqp0jfafg',
|
||||
signature:
|
||||
'IFytRg6KpvTHCzcW0ZwVhPqdKtRGpoRDcuEb958yIgJFUJlb1F5qPzt/JnlYE7r012BSFj+UT67DZVTU8oNB5vw='
|
||||
}
|
||||
await uut.createEntry(inputData)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, "Property 'category' must be a string!")
|
||||
}
|
||||
})
|
||||
|
||||
it('should catch and throw DB errors', async () => {
|
||||
try {
|
||||
// Force an error with the database.
|
||||
sandbox.stub(uut, 'EntryModel').throws(new Error('test error'))
|
||||
|
||||
const inputData = {
|
||||
entry: 'entry',
|
||||
description: 'test',
|
||||
slpAddress: 'simpleledger:qpnty9t0w93fez04h7yzevujpv8pun204qqp0jfafg',
|
||||
signature:
|
||||
'IFytRg6KpvTHCzcW0ZwVhPqdKtRGpoRDcuEb958yIgJFUJlb1F5qPzt/JnlYE7r012BSFj+UT67DZVTU8oNB5vw=',
|
||||
category: 'test'
|
||||
}
|
||||
|
||||
await uut.createEntry(inputData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw error if signature is invalid', async () => {
|
||||
try {
|
||||
// Mocking bchjs functions
|
||||
sandbox.stub(uut.bch, '_verifySignature').callsFake(() => {
|
||||
return false
|
||||
})
|
||||
|
||||
const inputData = {
|
||||
entry: 'entry',
|
||||
description: 'test',
|
||||
slpAddress: 'simpleledger:qpnty9t0w93fez04h7yzevujpv8pun204qqp0jfafg',
|
||||
signature:
|
||||
'IFytRg6KpvTHCzcW0ZwVhPqdKtRGpoRDcuEb958yIgJFUJlb1F5qPzt/JnlYE7r012BSFj+UT67DZVTU8oNB5vw=',
|
||||
category: 'test'
|
||||
}
|
||||
|
||||
await uut.createEntry(inputData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Invalid signature')
|
||||
}
|
||||
})
|
||||
it('should throw error for insufficient psf balance', async () => {
|
||||
try {
|
||||
// Mocking bchjs functions
|
||||
sandbox.stub(uut.bch, 'getPSFTokenBalance').resolves(0)
|
||||
|
||||
const inputData = {
|
||||
entry: 'entry',
|
||||
description: 'test',
|
||||
slpAddress: 'simpleledger:qpnty9t0w93fez04h7yzevujpv8pun204qqp0jfafg',
|
||||
signature:
|
||||
'IFytRg6KpvTHCzcW0ZwVhPqdKtRGpoRDcuEb958yIgJFUJlb1F5qPzt/JnlYE7r012BSFj+UT67DZVTU8oNB5vw=',
|
||||
category: 'test'
|
||||
}
|
||||
|
||||
await uut.createEntry(inputData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Insufficient psf balance')
|
||||
}
|
||||
})
|
||||
|
||||
it('should create a new entry in the DB', async () => {
|
||||
// Mocking bchjs functions
|
||||
// sandbox.stub(uut.bchjs, '_verifySignature').resolves(true)
|
||||
// sandbox.stub(uut.bchjs, 'getPSFTokenBalance').resolves(100)
|
||||
// sandbox.stub(uut.bchjs, 'getMerit').resolves(100)
|
||||
|
||||
const inputData = {
|
||||
entry: 'entry',
|
||||
description: 'test',
|
||||
slpAddress: 'simpleledger:qpnty9t0w93fez04h7yzevujpv8pun204qqp0jfafg',
|
||||
signature:
|
||||
'IFytRg6KpvTHCzcW0ZwVhPqdKtRGpoRDcuEb958yIgJFUJlb1F5qPzt/JnlYE7r012BSFj+UT67DZVTU8oNB5vw=',
|
||||
category: 'test'
|
||||
}
|
||||
|
||||
const result = await uut.createEntry(inputData)
|
||||
|
||||
assert.property(result, 'entry')
|
||||
assert.isString(result.entry)
|
||||
|
||||
assert.property(result, 'slpAddress')
|
||||
assert.isString(result.slpAddress)
|
||||
|
||||
assert.property(result, 'description')
|
||||
assert.isString(result.description)
|
||||
|
||||
assert.property(result, 'signature')
|
||||
assert.isString(result.signature)
|
||||
|
||||
assert.property(result, 'category')
|
||||
assert.isString(result.category)
|
||||
|
||||
assert.property(result, 'balance')
|
||||
assert.isNumber(result.balance)
|
||||
|
||||
assert.property(result, 'merit')
|
||||
assert.isNumber(result.merit)
|
||||
})
|
||||
})
|
||||
// describe('#createEntry', () => {
|
||||
// it('should throw an error if entry is not provided', async () => {
|
||||
// try {
|
||||
// await uut.createEntry({})
|
||||
// assert.fail('Unexpected code path')
|
||||
// } catch (err) {
|
||||
// assert.include(err.message, "Property 'entry' must be a string!")
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// it('should throw an error if description is not provided', async () => {
|
||||
// try {
|
||||
// const inputData = {
|
||||
// entry: 'entry'
|
||||
// }
|
||||
// await uut.createEntry(inputData)
|
||||
// assert.fail('Unexpected code path')
|
||||
// } catch (err) {
|
||||
// assert.include(err.message, "Property 'description' must be a string!")
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// it('should throw an error if slpAddress is not provided', async () => {
|
||||
// try {
|
||||
// const inputData = {
|
||||
// entry: 'entry',
|
||||
// description: 'test'
|
||||
// }
|
||||
// await uut.createEntry(inputData)
|
||||
// assert.fail('Unexpected code path')
|
||||
// } catch (err) {
|
||||
// assert.include(err.message, "Property 'slpAddress' must be a string!")
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// it('should throw an error if signature is not provided', async () => {
|
||||
// try {
|
||||
// const inputData = {
|
||||
// entry: 'entry',
|
||||
// description: 'test',
|
||||
// slpAddress: 'simpleledger:qpnty9t0w93fez04h7yzevujpv8pun204qqp0jfafg'
|
||||
// }
|
||||
// await uut.createEntry(inputData)
|
||||
// assert.fail('Unexpected code path')
|
||||
// } catch (err) {
|
||||
// assert.include(err.message, "Property 'signature' must be a string!")
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// it('should throw an error if category is not provided', async () => {
|
||||
// try {
|
||||
// const inputData = {
|
||||
// entry: 'entry',
|
||||
// description: 'test',
|
||||
// slpAddress: 'simpleledger:qpnty9t0w93fez04h7yzevujpv8pun204qqp0jfafg',
|
||||
// signature:
|
||||
// 'IFytRg6KpvTHCzcW0ZwVhPqdKtRGpoRDcuEb958yIgJFUJlb1F5qPzt/JnlYE7r012BSFj+UT67DZVTU8oNB5vw='
|
||||
// }
|
||||
// await uut.createEntry(inputData)
|
||||
// assert.fail('Unexpected code path')
|
||||
// } catch (err) {
|
||||
// assert.include(err.message, "Property 'category' must be a string!")
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// it('should catch and throw DB errors', async () => {
|
||||
// try {
|
||||
// // Force an error with the database.
|
||||
// sandbox.stub(uut, 'EntryModel').throws(new Error('test error'))
|
||||
//
|
||||
// const inputData = {
|
||||
// entry: 'entry',
|
||||
// description: 'test',
|
||||
// slpAddress: 'simpleledger:qpnty9t0w93fez04h7yzevujpv8pun204qqp0jfafg',
|
||||
// signature:
|
||||
// 'IFytRg6KpvTHCzcW0ZwVhPqdKtRGpoRDcuEb958yIgJFUJlb1F5qPzt/JnlYE7r012BSFj+UT67DZVTU8oNB5vw=',
|
||||
// category: 'test'
|
||||
// }
|
||||
//
|
||||
// await uut.createEntry(inputData)
|
||||
//
|
||||
// assert.fail('Unexpected code path')
|
||||
// } catch (err) {
|
||||
// assert.include(err.message, 'test error')
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// it('should throw error if signature is invalid', async () => {
|
||||
// try {
|
||||
// // Mocking bchjs functions
|
||||
// sandbox.stub(uut.bch, '_verifySignature').callsFake(() => {
|
||||
// return false
|
||||
// })
|
||||
//
|
||||
// const inputData = {
|
||||
// entry: 'entry',
|
||||
// description: 'test',
|
||||
// slpAddress: 'simpleledger:qpnty9t0w93fez04h7yzevujpv8pun204qqp0jfafg',
|
||||
// signature:
|
||||
// 'IFytRg6KpvTHCzcW0ZwVhPqdKtRGpoRDcuEb958yIgJFUJlb1F5qPzt/JnlYE7r012BSFj+UT67DZVTU8oNB5vw=',
|
||||
// category: 'test'
|
||||
// }
|
||||
//
|
||||
// await uut.createEntry(inputData)
|
||||
//
|
||||
// assert.fail('Unexpected code path')
|
||||
// } catch (err) {
|
||||
// assert.include(err.message, 'Invalid signature')
|
||||
// }
|
||||
// })
|
||||
// it('should throw error for insufficient psf balance', async () => {
|
||||
// try {
|
||||
// // Mocking bchjs functions
|
||||
// sandbox.stub(uut.bch, 'getPSFTokenBalance').resolves(0)
|
||||
//
|
||||
// const inputData = {
|
||||
// entry: 'entry',
|
||||
// description: 'test',
|
||||
// slpAddress: 'simpleledger:qpnty9t0w93fez04h7yzevujpv8pun204qqp0jfafg',
|
||||
// signature:
|
||||
// 'IFytRg6KpvTHCzcW0ZwVhPqdKtRGpoRDcuEb958yIgJFUJlb1F5qPzt/JnlYE7r012BSFj+UT67DZVTU8oNB5vw=',
|
||||
// category: 'test'
|
||||
// }
|
||||
//
|
||||
// await uut.createEntry(inputData)
|
||||
//
|
||||
// assert.fail('Unexpected code path')
|
||||
// } catch (err) {
|
||||
// assert.include(err.message, 'Insufficient psf balance')
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// it('should create a new entry in the DB', async () => {
|
||||
// // Mocking bchjs functions
|
||||
// // sandbox.stub(uut.bchjs, '_verifySignature').resolves(true)
|
||||
// // sandbox.stub(uut.bchjs, 'getPSFTokenBalance').resolves(100)
|
||||
// // sandbox.stub(uut.bchjs, 'getMerit').resolves(100)
|
||||
//
|
||||
// const inputData = {
|
||||
// entry: 'entry',
|
||||
// description: 'test',
|
||||
// slpAddress: 'simpleledger:qpnty9t0w93fez04h7yzevujpv8pun204qqp0jfafg',
|
||||
// signature:
|
||||
// 'IFytRg6KpvTHCzcW0ZwVhPqdKtRGpoRDcuEb958yIgJFUJlb1F5qPzt/JnlYE7r012BSFj+UT67DZVTU8oNB5vw=',
|
||||
// category: 'test'
|
||||
// }
|
||||
//
|
||||
// const result = await uut.createEntry(inputData)
|
||||
//
|
||||
// assert.property(result, 'entry')
|
||||
// assert.isString(result.entry)
|
||||
//
|
||||
// assert.property(result, 'slpAddress')
|
||||
// assert.isString(result.slpAddress)
|
||||
//
|
||||
// assert.property(result, 'description')
|
||||
// assert.isString(result.description)
|
||||
//
|
||||
// assert.property(result, 'signature')
|
||||
// assert.isString(result.signature)
|
||||
//
|
||||
// assert.property(result, 'category')
|
||||
// assert.isString(result.category)
|
||||
//
|
||||
// assert.property(result, 'balance')
|
||||
// assert.isNumber(result.balance)
|
||||
//
|
||||
// assert.property(result, 'merit')
|
||||
// assert.isNumber(result.merit)
|
||||
// })
|
||||
// })
|
||||
})
|
||||
|
||||
@@ -11,6 +11,7 @@ const sinon = require('sinon')
|
||||
|
||||
// Unit under test (uut)
|
||||
const OfferLib = require('../../../src/use-cases/offer')
|
||||
const OrderUseCase = require('../../../src/use-cases/order')
|
||||
const adapters = require('../mocks/adapters')
|
||||
|
||||
describe('#offer-use-case', () => {
|
||||
@@ -25,7 +26,8 @@ describe('#offer-use-case', () => {
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
uut = new OfferLib({ adapters })
|
||||
const order = new OrderUseCase({ adapters })
|
||||
uut = new OfferLib({ adapters, order })
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
@@ -87,12 +89,13 @@ describe('#offer-use-case', () => {
|
||||
tokenId:
|
||||
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
|
||||
buyOrSell: 'sell',
|
||||
rateInSats: 1000,
|
||||
minSatsToExchange: 10,
|
||||
rateInBaseUnit: 1000,
|
||||
minUnitsToExchange: 10,
|
||||
numTokens: 0.02,
|
||||
utxoTxid:
|
||||
'241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
|
||||
utxoVout: 0
|
||||
utxoVout: 0,
|
||||
makerAddr: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00'
|
||||
},
|
||||
timestamp: '2021-09-20T17:54:26.395Z',
|
||||
localTimeStamp: '9/20/2021, 10:54:26 AM',
|
||||
|
||||
@@ -65,34 +65,34 @@ describe('#order-use-case', () => {
|
||||
})
|
||||
})
|
||||
|
||||
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('#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',
|
||||
// rateInBaseUnit: 1000,
|
||||
// minUnitsToExchange: 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 () => {
|
||||
@@ -102,8 +102,8 @@ describe('#order-use-case', () => {
|
||||
messageClass: 1,
|
||||
tokenId: 'token-id',
|
||||
buyOrSell: 'sell',
|
||||
rateInSats: 1000,
|
||||
minSatsToExchange: 1250,
|
||||
rateInBaseUnit: 1000,
|
||||
minUnitsToExchange: 1250,
|
||||
numTokens: 1
|
||||
}
|
||||
|
||||
@@ -111,7 +111,7 @@ describe('#order-use-case', () => {
|
||||
// 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, 'moveTokens').resolves({ txid: 'fakeTxid', vout: 0, hdIndex: 1 })
|
||||
sandbox.stub(uut.adapters.wallet.bchWallet, 'getUtxos').resolves()
|
||||
// sandbox
|
||||
// .stub(uut.adapters.wallet, 'generateSignature')
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
This script has not been customized for offers yet.
|
||||
*/
|
||||
|
||||
const mongoose = require('mongoose')
|
||||
|
||||
// Force test environment
|
||||
// make sure environment variable is set before this file gets called.
|
||||
// see test script in package.json.
|
||||
// process.env.KOA_ENV = 'test'
|
||||
const config = require('../../config')
|
||||
|
||||
const User = require('../../src/models/users')
|
||||
|
||||
async function deleteUsers () {
|
||||
// Connect to the Mongo Database.
|
||||
mongoose.Promise = global.Promise
|
||||
mongoose.set('useCreateIndex', true) // Stop deprecation warning.
|
||||
await mongoose.connect(config.database, {
|
||||
useUnifiedTopology: true,
|
||||
useNewUrlParser: true
|
||||
})
|
||||
|
||||
// Get all the users in the DB.
|
||||
const users = await User.find({}, '-password')
|
||||
// console.log(`users: ${JSON.stringify(users, null, 2)}`)
|
||||
|
||||
// Delete each user.
|
||||
for (let i = 0; i < users.length; i++) {
|
||||
const thisUser = users[i]
|
||||
await thisUser.remove()
|
||||
}
|
||||
|
||||
mongoose.connection.close()
|
||||
}
|
||||
|
||||
deleteUsers()
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
Get all Offers in the database.
|
||||
*/
|
||||
|
||||
const mongoose = require('mongoose')
|
||||
|
||||
const config = require('../../config')
|
||||
|
||||
const Offer = require('../../src/adapters/localdb/models/offer')
|
||||
|
||||
async function getOffers () {
|
||||
// Connect to the Mongo Database.
|
||||
mongoose.Promise = global.Promise
|
||||
mongoose.set('useCreateIndex', true) // Stop deprecation warning.
|
||||
await mongoose.connect(config.database, {
|
||||
useNewUrlParser: true,
|
||||
useUnifiedTopology: true
|
||||
})
|
||||
|
||||
const offers = await Offer.find({})
|
||||
console.log(`offers: ${JSON.stringify(offers, null, 2)}`)
|
||||
|
||||
mongoose.connection.close()
|
||||
}
|
||||
getOffers()
|
||||
@@ -13,6 +13,9 @@ const BchTokenSweep = require('bch-token-sweep/index')
|
||||
// Local libraries
|
||||
const WalletAdapter = require('../../src/adapters/wallet')
|
||||
|
||||
// Constants
|
||||
const EMTPY_ADDR_CUTOFF = 5
|
||||
|
||||
async function sweepFunds () {
|
||||
try {
|
||||
// Open the wallet files.
|
||||
@@ -35,9 +38,11 @@ async function sweepFunds () {
|
||||
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 cashAddress = bchjs.HDNode.toCashAddress(childNode)
|
||||
const wifToSweep = bchjs.HDNode.toWIF(childNode)
|
||||
|
||||
console.log(`\nSweeping ${cashAddress}`)
|
||||
|
||||
try {
|
||||
// Sweep tokens from address
|
||||
const sweeper = new BchTokenSweep(
|
||||
@@ -62,14 +67,16 @@ async function sweepFunds () {
|
||||
// Wait between loop iterations.
|
||||
await bchjs.Util.sleep(3000)
|
||||
} catch (err) {
|
||||
console.log(`error message: ${err.message}`)
|
||||
console.log(`error message with index ${hdIndex}: ${err.message}`)
|
||||
emptyAddrCnt++
|
||||
}
|
||||
|
||||
hdIndex++
|
||||
} while (emptyAddrCnt < 5)
|
||||
} while (emptyAddrCnt < EMTPY_ADDR_CUTOFF)
|
||||
|
||||
console.log('5 empty addresses detected. Exiting.')
|
||||
console.log(`${EMTPY_ADDR_CUTOFF} empty addresses detected. Exiting.`)
|
||||
|
||||
console.log('\n\nDo not forget to reset the nextAddress property in the wallet.json file!\n\n')
|
||||
} catch (err) {
|
||||
console.error('Error in sweepFunds(): ', err)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user