mirror of
https://github.com/Permissionless-Software-Foundation/bch-dex.git
synced 2026-09-22 17:12:03 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fb00c1bc86 | ||
|
|
7964bfbae7 | ||
|
|
a13a1c91e8 | ||
|
|
b19d4da345 | ||
|
|
443f405f56 | ||
|
|
61bb5169b3 | ||
|
|
f812440555 | ||
|
|
693c98fb15 | ||
|
|
fdf73610eb | ||
|
|
b09e77c079 | ||
|
|
1a4c602234 | ||
|
|
dedc6e2d30 | ||
|
|
68ad2561b1 | ||
|
|
791d11e597 | ||
|
|
5489e76f55 | ||
|
|
496a1dd07c | ||
|
|
a7b8ca9ad5 | ||
|
|
6eb16eb11a | ||
|
|
d1fb676a8f |
Vendored
+5
-3
@@ -15,7 +15,7 @@ const ipfsCoordName = process.env.COORD_NAME
|
||||
|
||||
module.exports = {
|
||||
// Configure TCP port.
|
||||
port: process.env.PORT || 5002,
|
||||
port: process.env.PORT || 5700,
|
||||
|
||||
// Password for HTML UI that displays logs.
|
||||
logPass: 'test',
|
||||
@@ -37,6 +37,9 @@ module.exports = {
|
||||
? process.env.CONSUMER_URL
|
||||
: 'https://free-bch.fullstack.cash',
|
||||
|
||||
// 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',
|
||||
|
||||
// FullStack.cash account information, used for automatic JWT handling.
|
||||
getJwtAtStartup: process.env.GET_JWT_AT_STARTUP ? true : false,
|
||||
authServer: process.env.AUTHSERVER
|
||||
@@ -67,8 +70,7 @@ module.exports = {
|
||||
name: ipfsCoordName,
|
||||
version,
|
||||
protocol: 'generic-service',
|
||||
description:
|
||||
'This is a generic IPFS Serivice Provider that uses JSON RPC over IPFS to communicate with it. This instance has not been customized. Source code: https://github.com/Permissionless-Software-Foundation/ipfs-service-provider',
|
||||
description: 'This is a generic IPFS Serivice Provider that uses JSON RPC over IPFS to communicate with it. This instance has not been customized. Source code: https://github.com/Permissionless-Software-Foundation/ipfs-service-provider',
|
||||
documentation: 'https://ipfs-service-provider.fullstack.cash/',
|
||||
provider: {
|
||||
'@type': 'Organization',
|
||||
|
||||
Vendored
+1
-1
@@ -7,6 +7,6 @@
|
||||
module.exports = {
|
||||
session: 'secret-boilerplate-token',
|
||||
token: 'secret-jwt-token',
|
||||
database: 'mongodb://localhost:27017/swap-service-dev',
|
||||
database: 'mongodb://localhost:27017/bch-swap-service-dev',
|
||||
env: 'dev'
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -13,6 +13,6 @@ module.exports = {
|
||||
// database: 'mongodb://172.17.0.1:5555/ipfs-service-prod',
|
||||
database: process.env.DBURL
|
||||
? process.env.DBURL
|
||||
: 'mongodb://172.17.0.1:5555/swap-service-prod',
|
||||
: 'mongodb://172.17.0.1:5555/bch-swap-service-prod',
|
||||
env: 'prod'
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -7,6 +7,6 @@
|
||||
module.exports = {
|
||||
session: 'secret-boilerplate-token',
|
||||
token: 'secret-jwt-token',
|
||||
database: 'mongodb://localhost:27017/swap-service-test',
|
||||
database: 'mongodb://localhost:27017/bch-swap-service-test',
|
||||
env: 'test'
|
||||
}
|
||||
|
||||
+4
-4
@@ -47,15 +47,15 @@ These are just a brief, high-level overview. Review the [Specifications](./speci
|
||||
|
||||
## Writing to the Global Database
|
||||
|
||||
Adding data to the global P2WDB is triggered by the _Client_ calling a REST API endpoint on `bch-dex`. `bch-dex` [p2wdb npm library](https://www.npmjs.com/package/p2wdb) can be leveraged for easy reading and writing to the P2WDB.
|
||||
Adding data to the global P2WDB is triggered by the _Client_ calling a REST API endpoint on `bch-dex`. The [p2wdb npm library](https://www.npmjs.com/package/p2wdb) can be leveraged for easy reading and writing to the P2WDB.
|
||||
|
||||
Writing data follows these steps:
|
||||
|
||||
- Tokens and BCH are held by a wallet which is under the controlled of `bch-dex`.
|
||||
- The _Client_ submits data to the POST `/offer` REST API endpoint to create a new Offer.
|
||||
- `bch-dex` will move the funds into a segregated UTXO, and will use that UTXO to create an Offer. The Offer data is written to the P2WDB.
|
||||
- `bch-dex` will receive a webhook call to its POST `/order` endpoint by the P2WDB. This event will trigger the import of the new data into the apps local Mongo database, and generate a new Order model.
|
||||
- This webhook event is mirrored by every instance of `bch-dex` on the network. Each P2WDB peer on the network will independently validate the new database entry.
|
||||
- `bch-dex` will move the funds into a segregated UTXO, and will use that UTXO to create an Offer. The Offer data is written to the P2WDB. The Offer data is also saved to the local MongoDB.
|
||||
- After submitting the data to the P2WDB, `bch-dex` will receive a webhook call to its POST `/order` endpoint by the P2WDB. This event will trigger the import of the new data into the apps local Mongo database, and generate a new Order model.
|
||||
- This webhook event is mirrored by every instance of `bch-dex` on the network. Each P2WDB peer on the network will independently validate the new database entry and create a new Order model.
|
||||
|
||||
|
||||
## Reading from the Local Database
|
||||
|
||||
Generated
+3070
-11755
File diff suppressed because it is too large
Load Diff
+4
-3
@@ -9,7 +9,7 @@
|
||||
"test:all": "export BCH_DEX=test && nyc --reporter=text mocha --exit --timeout 15000 --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:integration": "export BCH_DEX=test && mocha --exit --timeout 30000 --recursive test/integration",
|
||||
"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",
|
||||
"docs": "./node_modules/.bin/apidoc -i src/ -o docs",
|
||||
@@ -33,7 +33,7 @@
|
||||
"bcryptjs": "2.4.3",
|
||||
"glob": "7.1.6",
|
||||
"ipfs": "0.60.0",
|
||||
"ipfs-coord": "6.8.12",
|
||||
"ipfs-coord": "7.1.4",
|
||||
"ipfs-http-client": "55.0.0",
|
||||
"jsonrpc-lite": "2.2.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
@@ -51,10 +51,11 @@
|
||||
"koa2-ratelimit": "0.9.1",
|
||||
"libp2p": "^0.36.2",
|
||||
"line-reader": "0.4.0",
|
||||
"minimal-slp-wallet": "../minimal-slp-wallet",
|
||||
"minimal-slp-wallet": "4.4.2",
|
||||
"mongoose": "5.13.13",
|
||||
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
|
||||
"nodemailer": "6.4.17",
|
||||
"p2wdb": "1.3.1",
|
||||
"passport-local": "1.0.0",
|
||||
"public-ip": "^4.0.4",
|
||||
"winston": "3.3.3",
|
||||
|
||||
@@ -18,7 +18,7 @@ const JSONFiles = require('./json-files')
|
||||
const FullStackJWT = require('./fullstack-jwt')
|
||||
const BCHAdapter = require('./bch')
|
||||
const WalletAdapter = require('./wallet')
|
||||
const P2wdbAdapter = require('./p2wdb')
|
||||
const P2wdbAdapter = require('./p2wdb-adapter')
|
||||
|
||||
//
|
||||
// // Instantiate adapter libraries.
|
||||
@@ -52,10 +52,11 @@ class Adapters {
|
||||
this.nodemailer = new Nodemailer()
|
||||
this.jsonFiles = new JSONFiles()
|
||||
this.bchjs = new BCHJS()
|
||||
localConfig.bchjs = this.bchjs
|
||||
this.bch = new BCHAdapter()
|
||||
this.config = config
|
||||
this.wallet = new WalletAdapter()
|
||||
this.p2wdb = new P2wdbAdapter()
|
||||
this.p2wdb = new P2wdbAdapter(localConfig)
|
||||
|
||||
// Get a valid JWT API key and instance bch-js.
|
||||
this.fullStackJwt = new FullStackJWT(config)
|
||||
|
||||
@@ -91,7 +91,7 @@ class IpfsCoordAdapter {
|
||||
attachRPCRouter (router) {
|
||||
try {
|
||||
_this.ipfsCoord.privateLog = router
|
||||
_this.ipfsCoord.adapters.orbit.privateLog = router
|
||||
_this.ipfsCoord.adapters.pubsub.privateLog = router
|
||||
} catch (err) {
|
||||
console.error('Error in attachRPCRouter()')
|
||||
throw err
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
const Users = require('./models/users')
|
||||
const Entries = require('./models/entry')
|
||||
const Order = require('./models/order')
|
||||
const Offer = require('./models/offer')
|
||||
|
||||
class LocalDB {
|
||||
constructor () {
|
||||
@@ -13,6 +14,7 @@ class LocalDB {
|
||||
this.Users = Users
|
||||
this.Entry = Entries
|
||||
this.Order = Order
|
||||
this.Offer = Offer
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,6 +14,7 @@ const Offer = new mongoose.Schema({
|
||||
utxoTxid: { type: String },
|
||||
utxoVout: { type: Number },
|
||||
numTokens: { type: Number },
|
||||
hdIndex: { type: Number }, // HD index address holding the UTXO for this offer.
|
||||
|
||||
//
|
||||
offerIpfsId: { type: String },
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
Adapter library for interacting with the P2WDB
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const axios = require('axios')
|
||||
const { Write, Read } = require('p2wdb/index')
|
||||
|
||||
// Local libraries
|
||||
const config = require('../../config')
|
||||
|
||||
class P2wdbAdapter {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency injection
|
||||
this.bchjs = localConfig.bchjs
|
||||
if (!this.bchjs) {
|
||||
throw new Error('Must pass an instance of bch-js when instantiating p2wdb.js adapter.')
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.axios = axios
|
||||
this.config = config
|
||||
this.Write = Write
|
||||
this.Read = Read
|
||||
|
||||
// Allow the localConfig to overwrite the config file values.
|
||||
this.p2wdbURL = localConfig.p2wdbURL || config.p2wdbUrl
|
||||
}
|
||||
|
||||
// Write some data to the P2WDB
|
||||
async write (inputObj = {}) {
|
||||
try {
|
||||
const { appId = 'test', data = {}, wif } = inputObj
|
||||
|
||||
if (typeof wif !== 'string' || !wif) {
|
||||
throw new Error('wif input must be a private key starting with the letter L or K')
|
||||
}
|
||||
|
||||
const p2write = this.instantiateWriteLib(wif)
|
||||
|
||||
const result = await p2write.postEntry(data, appId)
|
||||
|
||||
return result.hash
|
||||
} catch (err) {
|
||||
console.error('Error in p2wdb.js/write()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Instantiate the Write library with a WIF, handling the various config
|
||||
// settings.
|
||||
instantiateWriteLib (wif) {
|
||||
try {
|
||||
// Object used to configure the Write library.
|
||||
let configObj = {}
|
||||
|
||||
if (this.config.useFullStackCash) {
|
||||
// Use web 2 infrastructure.
|
||||
// bch-js and FullStack.cash
|
||||
|
||||
configObj = {
|
||||
wif,
|
||||
serverURL: this.p2wdbURL,
|
||||
restURL: this.bchjs.restURL,
|
||||
apiToken: this.bchjs.apiToken
|
||||
}
|
||||
} else {
|
||||
// Use web 3 infrastructure.
|
||||
// ipfs-bch-wallet-consumer and bch-consumer library
|
||||
|
||||
configObj = {
|
||||
wif,
|
||||
serverURL: this.p2wdbURL,
|
||||
interface: 'consumer-api',
|
||||
restURL: this.config.consumerUrl
|
||||
}
|
||||
}
|
||||
|
||||
const write = new this.Write(configObj)
|
||||
|
||||
return write
|
||||
} catch (err) {
|
||||
console.error('Error in p2wdb-adapter.js/instantitateWriteLib()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a promise that resolves to true or false, to indicate if the
|
||||
// WIF controls the required funds to write to the P2WDB.
|
||||
async checkForSufficientFunds (wif) {
|
||||
try {
|
||||
if (typeof wif !== 'string' || !wif) {
|
||||
throw new Error('invalid wif to check for funds')
|
||||
}
|
||||
|
||||
// Instatiate the Write library
|
||||
const p2write = this.instantiateWriteLib(wif)
|
||||
|
||||
return await p2write.checkForSufficientFunds()
|
||||
} catch (err) {
|
||||
console.error('Error in p2wdb.js/checkForSufficientFunds()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = P2wdbAdapter
|
||||
@@ -1,57 +0,0 @@
|
||||
/*
|
||||
Adapter library for interacting with the P2WDB
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const axios = require('axios')
|
||||
|
||||
// Local libraries
|
||||
const config = require('../../config')
|
||||
|
||||
// Global constants
|
||||
let P2WDB_SERVER = 'http://localhost:5001/entry/write'
|
||||
// const P2WDB_SERVER = 'https://p2wdb.fullstack.cash/entry/write'
|
||||
|
||||
class P2wdbAdapter {
|
||||
constructor (localConfig = {}) {
|
||||
// Encapsulate dependencies
|
||||
this.axios = axios
|
||||
this.config = config
|
||||
|
||||
P2WDB_SERVER = `http://localhost:${config.p2wdbPort}/entry/write`
|
||||
}
|
||||
|
||||
async write (inputObj) {
|
||||
try {
|
||||
const { txid, signature, message, appId, data } = inputObj
|
||||
|
||||
// TODO: Input validation
|
||||
|
||||
const now = new Date()
|
||||
|
||||
const dataObj = {
|
||||
appId,
|
||||
data,
|
||||
timestamp: now.toISOString(),
|
||||
localTimeStamp: now.toLocaleString()
|
||||
}
|
||||
|
||||
const bodyData = {
|
||||
txid,
|
||||
message,
|
||||
signature,
|
||||
data: JSON.stringify(dataObj)
|
||||
}
|
||||
|
||||
const result = await this.axios.post(P2WDB_SERVER, bodyData)
|
||||
// console.log(`Response from API: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
return result.data.hash
|
||||
} catch (err) {
|
||||
console.error('Error in p2wdb.js/write()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = P2wdbAdapter
|
||||
+60
-60
@@ -10,9 +10,9 @@ const JsonFiles = require('./json-files')
|
||||
const config = require('../../config')
|
||||
|
||||
const WALLET_FILE = `${__dirname.toString()}/../../wallet.json`
|
||||
const PROOF_OF_BURN_QTY = 0.01
|
||||
const P2WDB_TOKEN_ID =
|
||||
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0'
|
||||
// const PROOF_OF_BURN_QTY = 0.01
|
||||
// const P2WDB_TOKEN_ID =
|
||||
// '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0'
|
||||
|
||||
class WalletAdapter {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -178,63 +178,63 @@ class WalletAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
// Burn enough PSF to generate a valide proof-of-burn for writing to the P2WDB.
|
||||
async burnPsf () {
|
||||
try {
|
||||
// TODO: Throw error if this.bchWallet has not been instantiated.
|
||||
|
||||
// console.log('walletData: ', walletData)
|
||||
// console.log(
|
||||
// `walletData.utxos.utxoStore.slpUtxos: ${JSON.stringify(
|
||||
// walletData.utxos.utxoStore.slpUtxos,
|
||||
// null,
|
||||
// 2,
|
||||
// )}`,
|
||||
// )
|
||||
|
||||
// Get token UTXOs held by the wallet.
|
||||
const tokenUtxos = this.bchWallet.utxos.utxoStore.slpUtxos.type1.tokens
|
||||
// console.log(`tokenUtxos: ${JSON.stringify(tokenUtxos, null, 2)}`)
|
||||
|
||||
// Find a token UTXO that contains PSF with a quantity higher than needed
|
||||
// to generate a proof-of-burn.
|
||||
let tokenUtxo = {}
|
||||
for (let i = 0; i < tokenUtxos.length; i++) {
|
||||
const thisUtxo = tokenUtxos[i]
|
||||
|
||||
// If token ID matches.
|
||||
if (thisUtxo.tokenId === P2WDB_TOKEN_ID) {
|
||||
if (parseFloat(thisUtxo.qtyStr) >= PROOF_OF_BURN_QTY) {
|
||||
tokenUtxo = thisUtxo
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (tokenUtxo.tokenId !== P2WDB_TOKEN_ID) {
|
||||
throw new Error(
|
||||
`Token UTXO of with ID of ${P2WDB_TOKEN_ID} and quantity greater than ${PROOF_OF_BURN_QTY} could not be found in wallet.`
|
||||
)
|
||||
}
|
||||
// console.log(`tokenUtxo: ${JSON.stringify(tokenUtxo, null, 2)}`)
|
||||
|
||||
const result = await this.bchWallet.burnTokens(
|
||||
PROOF_OF_BURN_QTY,
|
||||
P2WDB_TOKEN_ID
|
||||
)
|
||||
// console.log('walletData.burnTokens() result: ', result)
|
||||
|
||||
return result
|
||||
|
||||
// return {
|
||||
// success: true,
|
||||
// txid: 'fakeTxid',
|
||||
// }
|
||||
} catch (err) {
|
||||
console.error('Error in burnPsf(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
// Burn enough PSF to generate a valide proof-of-burn for writing to the P2WDB.
|
||||
// async burnPsf () {
|
||||
// try {
|
||||
// // TODO: Throw error if this.bchWallet has not been instantiated.
|
||||
//
|
||||
// // console.log('walletData: ', walletData)
|
||||
// // console.log(
|
||||
// // `walletData.utxos.utxoStore.slpUtxos: ${JSON.stringify(
|
||||
// // walletData.utxos.utxoStore.slpUtxos,
|
||||
// // null,
|
||||
// // 2,
|
||||
// // )}`,
|
||||
// // )
|
||||
//
|
||||
// // Get token UTXOs held by the wallet.
|
||||
// const tokenUtxos = this.bchWallet.utxos.utxoStore.slpUtxos.type1.tokens
|
||||
// // console.log(`tokenUtxos: ${JSON.stringify(tokenUtxos, null, 2)}`)
|
||||
//
|
||||
// // Find a token UTXO that contains PSF with a quantity higher than needed
|
||||
// // to generate a proof-of-burn.
|
||||
// let tokenUtxo = {}
|
||||
// for (let i = 0; i < tokenUtxos.length; i++) {
|
||||
// const thisUtxo = tokenUtxos[i]
|
||||
//
|
||||
// // If token ID matches.
|
||||
// if (thisUtxo.tokenId === P2WDB_TOKEN_ID) {
|
||||
// if (parseFloat(thisUtxo.qtyStr) >= PROOF_OF_BURN_QTY) {
|
||||
// tokenUtxo = thisUtxo
|
||||
// break
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// if (tokenUtxo.tokenId !== P2WDB_TOKEN_ID) {
|
||||
// throw new Error(
|
||||
// `Token UTXO of with ID of ${P2WDB_TOKEN_ID} and quantity greater than ${PROOF_OF_BURN_QTY} could not be found in wallet.`
|
||||
// )
|
||||
// }
|
||||
// // console.log(`tokenUtxo: ${JSON.stringify(tokenUtxo, null, 2)}`)
|
||||
//
|
||||
// const result = await this.bchWallet.burnTokens(
|
||||
// PROOF_OF_BURN_QTY,
|
||||
// P2WDB_TOKEN_ID
|
||||
// )
|
||||
// // console.log('walletData.burnTokens() result: ', result)
|
||||
//
|
||||
// return result
|
||||
//
|
||||
// // return {
|
||||
// // success: true,
|
||||
// // txid: 'fakeTxid',
|
||||
// // }
|
||||
// } catch (err) {
|
||||
// console.error('Error in burnPsf(): ', err)
|
||||
// throw err
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
module.exports = WalletAdapter
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
/*
|
||||
This is the JSON RPC router for the users API
|
||||
|
||||
CT 3/5/22: This library can probably be deleted. Handling of the /about endpoint
|
||||
is now directly controlled by the ipfs-coord library.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
@@ -33,6 +36,8 @@ class AboutRPC {
|
||||
// This is a bit different than other router libraries, because there is
|
||||
// only one response, which is a string about this node.
|
||||
async aboutRouter (rpcData) {
|
||||
console.log('debugging: aboutRouter from ipfs-service-provider triggered')
|
||||
|
||||
return {
|
||||
success: true,
|
||||
status: 200,
|
||||
|
||||
@@ -49,6 +49,18 @@ class OrderRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
// curl -X GET http://localhost:5700/order/list
|
||||
async listOrders (ctx) {
|
||||
try {
|
||||
const orders = await _this.useCases.order.listOrders()
|
||||
|
||||
ctx.body = orders
|
||||
} catch (err) {
|
||||
console.log('Error in listOrders REST API handler.')
|
||||
_this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// DRY error handler
|
||||
handleError (ctx, err) {
|
||||
console.log('err', err.message)
|
||||
|
||||
@@ -50,6 +50,7 @@ class OrderRouter {
|
||||
|
||||
// Define the routes and attach the controller.
|
||||
this.router.post('/', _this.orderRESTController.createOrder)
|
||||
this.router.get('/list', _this.orderRESTController.listOrders)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(_this.router.routes())
|
||||
|
||||
+25
-23
@@ -47,30 +47,23 @@ class OfferLib {
|
||||
// Update the offer with the new UTXO information.
|
||||
offerEntity.utxoTxid = utxoInfo.txid
|
||||
offerEntity.utxoVout = utxoInfo.vout
|
||||
|
||||
// Burn PSF token to pay for P2WDB write.
|
||||
const txid = await this.adapters.wallet.burnPsf()
|
||||
console.log('burn txid: ', txid)
|
||||
console.log(`https://simpleledger.info/tx/${txid}`)
|
||||
|
||||
// generate signature.
|
||||
const now = new Date()
|
||||
const message = now.toISOString()
|
||||
const signature = await this.adapters.wallet.generateSignature(message)
|
||||
// console.log('signature: ', signature)
|
||||
|
||||
const p2wdbObj = {
|
||||
txid,
|
||||
signature,
|
||||
message,
|
||||
appId: this.config.p2wdbAppId,
|
||||
data: offerEntity
|
||||
}
|
||||
offerEntity.hdIndex = utxoInfo.hdIndex
|
||||
|
||||
// Add offer to P2WDB.
|
||||
const p2wdbObj = {
|
||||
wif: this.adapters.wallet.bchWallet.walletInfo.privateKey,
|
||||
data: offerEntity,
|
||||
appId: this.config.p2wdbAppId
|
||||
}
|
||||
const hash = await this.adapters.p2wdb.write(p2wdbObj)
|
||||
// console.log('hash: ', hash)
|
||||
|
||||
// Create a MongoDB model to hold the Offer
|
||||
offerEntity.p2wdbHash = hash
|
||||
console.log(`creating new offer model: ${JSON.stringify(offerEntity, null, 2)}`)
|
||||
const offer = new this.OfferModel(offerEntity)
|
||||
await offer.save()
|
||||
|
||||
return hash
|
||||
} catch (err) {
|
||||
// console.log("Error in use-cases/entry.js/createEntry()", err.message)
|
||||
@@ -97,7 +90,8 @@ class OfferLib {
|
||||
|
||||
const utxoInfo = {
|
||||
txid,
|
||||
vout: 0
|
||||
vout: 0,
|
||||
hdIndex: keyPair.hdIndex
|
||||
}
|
||||
|
||||
return utxoInfo
|
||||
@@ -112,6 +106,12 @@ class OfferLib {
|
||||
async ensureFunds (offerEntity) {
|
||||
try {
|
||||
// console.log('this.adapters.wallet: ', this.adapters.wallet.bchWallet)
|
||||
// console.log(`walletInfo: ${JSON.stringify(this.adapters.wallet.bchWallet.walletInfo, null, 2)}`)
|
||||
|
||||
// Ensure the app wallet has enough funds to write to the P2WDB.
|
||||
const wif = this.adapters.wallet.bchWallet.walletInfo.privateKey
|
||||
const canWriteToP2WDB = await this.adapters.p2wdb.checkForSufficientFunds(wif)
|
||||
if (!canWriteToP2WDB) throw new Error('App wallet does not have funds for writing to the P2WDB.')
|
||||
|
||||
// Get UTXOs.
|
||||
const utxos = this.adapters.wallet.bchWallet.utxos.utxoStore
|
||||
@@ -129,16 +129,18 @@ class OfferLib {
|
||||
// Get the total amount of tokens in the wallet that match the token
|
||||
// in the offer.
|
||||
let totalTokenBalance = 0
|
||||
tokenUtxos.map(x => (totalTokenBalance += parseFloat(x.tokenQty)))
|
||||
// console.log('totalTokenBalance: ', totalTokenBalance)
|
||||
tokenUtxos.map(x => (totalTokenBalance += parseFloat(x.qtyStr)))
|
||||
console.log('totalTokenBalance: ', totalTokenBalance)
|
||||
|
||||
// If there are fewer tokens in the wallet than what's in the offer,
|
||||
// throw an error.
|
||||
if (totalTokenBalance <= offerEntity.numTokens) {
|
||||
if (totalTokenBalance <= offerEntity.numTokens || isNaN(totalTokenBalance)) {
|
||||
throw new Error(
|
||||
'App wallet does not have enough tokens to satisfy the SELL offer.'
|
||||
)
|
||||
}
|
||||
|
||||
//
|
||||
} else {
|
||||
// Buy Offer
|
||||
throw new Error('Buy orders are not supported yet.')
|
||||
|
||||
@@ -56,6 +56,15 @@ class OrderUseCases {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async listOrders () {
|
||||
try {
|
||||
return this.OrderModel.find({})
|
||||
} catch (error) {
|
||||
console.error('Error in use-cases/order/listOrders()')
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OrderUseCases
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
const axios = require('axios')
|
||||
|
||||
const LOCALHOST = 'http://localhost:5002'
|
||||
const LOCALHOST = 'http://localhost:5700'
|
||||
|
||||
async function start () {
|
||||
try {
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
Integration tests for hte p2wdb-adapter.js library
|
||||
|
||||
These integration tests may not necessarily pass. They require a WIF set
|
||||
as an environment variable, and loaded with enough BCH and PSF to make
|
||||
a write to the P2WDB.
|
||||
|
||||
If these tests fail, it does not necessarily mean there is a problem with the
|
||||
code. Check that the WIF envrionment variable meets the requirements.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const BCHJS = require('@psf/bch-js')
|
||||
|
||||
// Local libraries
|
||||
const P2wdbAdapter = require('../../../src/adapters/p2wdb-adapter')
|
||||
|
||||
const wif = process.env.WIF
|
||||
if (!wif) {
|
||||
throw new Error('You must provide a BCH WIF private key as an environment variable to run this test.')
|
||||
}
|
||||
|
||||
describe('#p2wdb-adapter.js', () => {
|
||||
let uut
|
||||
|
||||
beforeEach(() => {
|
||||
const bchjs = new BCHJS()
|
||||
|
||||
uut = new P2wdbAdapter({ bchjs })
|
||||
})
|
||||
|
||||
describe('#checkForSufficientFunds', () => {
|
||||
it('should check for sufficient funds', async () => {
|
||||
const result = await uut.checkForSufficientFunds(wif)
|
||||
|
||||
console.log('Provided WIF has funds for making a write: ', result)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#write', () => {
|
||||
it('should write data to the P2WDB', async () => {
|
||||
const inObj = {
|
||||
appId: 'test234',
|
||||
data: {
|
||||
key: 'value'
|
||||
},
|
||||
wif
|
||||
}
|
||||
|
||||
const result = await uut.write(inObj)
|
||||
console.log('result: ', result)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
Integration tests for the offer-use-case.js library
|
||||
|
||||
For these tests to pass, the app wallet must have Trout test tokens with
|
||||
a token ID of a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const assert = require('chai').assert
|
||||
|
||||
// Local npm libraries
|
||||
const Adapters = require('../../../src/adapters')
|
||||
const Offer = require('../../../src/use-cases/offer')
|
||||
|
||||
describe('#offer-use-case.js', () => {
|
||||
let uut
|
||||
|
||||
before(async () => {
|
||||
const adapters = new Adapters()
|
||||
adapters.config.getJwtAtStartup = false
|
||||
await adapters.start()
|
||||
|
||||
uut = new Offer({ adapters })
|
||||
})
|
||||
|
||||
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',
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
||||
buyOrSell: 'sell',
|
||||
rateInSats: 1000,
|
||||
minSatsToExchange: 1250,
|
||||
numTokens: 1
|
||||
}
|
||||
|
||||
const result = await uut.ensureFunds(offerEntity)
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -74,8 +74,9 @@ describe('#IPFS', () => {
|
||||
}
|
||||
},
|
||||
adapters: {
|
||||
orbit: {
|
||||
privateLog: () => {}
|
||||
pubsub: {
|
||||
privateLog: () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -96,7 +97,7 @@ describe('#IPFS', () => {
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Cannot read property')
|
||||
assert.include(err.message, 'Cannot read')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -52,7 +52,8 @@ describe('#IPFS-adapter', () => {
|
||||
it('should stop the IPFS node', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfs = {
|
||||
stop: () => {}
|
||||
stop: () => {
|
||||
}
|
||||
}
|
||||
|
||||
const result = await uut.stop()
|
||||
@@ -61,24 +62,24 @@ describe('#IPFS-adapter', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#rmBlocksDir', () => {
|
||||
it('should delete the /blocks directory', () => {
|
||||
const result = uut.rmBlocksDir()
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should catch and throw an error', () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox.stub(uut.fs, 'rmdirSync').throws(new Error('test error'))
|
||||
|
||||
uut.rmBlocksDir()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.equal(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
// describe('#rmBlocksDir', () => {
|
||||
// it('should delete the /blocks directory', () => {
|
||||
// const result = uut.rmBlocksDir()
|
||||
//
|
||||
// assert.equal(result, true)
|
||||
// })
|
||||
//
|
||||
// it('should catch and throw an error', () => {
|
||||
// try {
|
||||
// // Force an error
|
||||
// sandbox.stub(uut.fs, 'rmdirSync').throws(new Error('test error'))
|
||||
//
|
||||
// uut.rmBlocksDir()
|
||||
//
|
||||
// assert.fail('Unexpected code path')
|
||||
// } catch (err) {
|
||||
// assert.equal(err.message, 'test error')
|
||||
// }
|
||||
// })
|
||||
// })
|
||||
})
|
||||
|
||||
@@ -5,48 +5,141 @@
|
||||
// Public npm libraries.
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
const BCHJS = require('@psf/bch-js')
|
||||
|
||||
// Local libraries.
|
||||
const P2wdbAdapter = require('../../../src/adapters/p2wdb')
|
||||
const P2wdbAdapter = require('../../../src/adapters/p2wdb-adapter')
|
||||
|
||||
describe('#P2wdbAdapter', () => {
|
||||
let uut, sandbox
|
||||
|
||||
beforeEach(() => {
|
||||
uut = new P2wdbAdapter()
|
||||
const bchjs = new BCHJS()
|
||||
|
||||
uut = new P2wdbAdapter({ bchjs })
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#write', () => {
|
||||
it('should write an offer to the P2WDB', async () => {
|
||||
const inputObj = {
|
||||
txid: 'testTxid',
|
||||
signature: 'testSignature',
|
||||
message: 'testMessage',
|
||||
appId: 'testAppId',
|
||||
data: { key: 'value' }
|
||||
describe('#constructor', () => {
|
||||
it('should throw error if bch-js is not passed in', () => {
|
||||
try {
|
||||
uut = new P2wdbAdapter()
|
||||
|
||||
assert.fail('Unexpected coded path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Must pass an instance of bch-js when instantiating p2wdb.js adapter.')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
// Mock axios to prevent live network call
|
||||
sandbox.stub(uut.axios, 'post').resolves({ data: { hash: 'testhash' } })
|
||||
describe('#instantiateWriteLib', () => {
|
||||
it('should instantiate Write library using web 3 interface', async () => {
|
||||
// Force code path
|
||||
uut.config.useFullStackCash = false
|
||||
|
||||
const result = await uut.write(inputObj)
|
||||
// Mock dependencies
|
||||
uut.Write =
|
||||
class Write {
|
||||
constructor () {
|
||||
this.interface = 'web3'
|
||||
}
|
||||
}
|
||||
|
||||
const wif = 'L1tcvcqa5PztqqDH4ZEcUmHA9aSHhTau5E2Zwp1xEK5CrKBrjP3m'
|
||||
|
||||
const result = uut.instantiateWriteLib(wif)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result, 'testhash')
|
||||
assert.equal(result.interface, 'web3')
|
||||
})
|
||||
|
||||
it('should instantiate Write library using web 2 interface', async () => {
|
||||
// Force code path
|
||||
uut.config.useFullStackCash = true
|
||||
|
||||
// Mock dependencies
|
||||
uut.Write =
|
||||
class Write {
|
||||
constructor () {
|
||||
this.interface = 'fullstack'
|
||||
}
|
||||
}
|
||||
|
||||
const wif = 'L1tcvcqa5PztqqDH4ZEcUmHA9aSHhTau5E2Zwp1xEK5CrKBrjP3m'
|
||||
|
||||
const result = uut.instantiateWriteLib(wif)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.interface, 'fullstack')
|
||||
})
|
||||
|
||||
it('should catch and throw errors', async () => {
|
||||
try {
|
||||
await uut.instantiateWriteLib()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, 'WIF private key required')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#checkForSufficientFunds', () => {
|
||||
it('should throw error if WIF is not provided', async () => {
|
||||
try {
|
||||
await uut.checkForSufficientFunds()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, 'invalid wif to check for funds')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return status of funds', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut, 'instantiateWriteLib').returns({
|
||||
checkForSufficientFunds: () => true
|
||||
})
|
||||
|
||||
const wif = 'L1tcvcqa5PztqqDH4ZEcUmHA9aSHhTau5E2Zwp1xEK5CrKBrjP3m'
|
||||
|
||||
const result = await uut.checkForSufficientFunds(wif)
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#write', async () => {
|
||||
it('should throw error if WIF is not provided', async () => {
|
||||
try {
|
||||
await uut.write()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, 'Cannot destructure property')
|
||||
assert.include(err.message, 'wif input must be a private key starting with the letter L or K')
|
||||
}
|
||||
})
|
||||
|
||||
it('should write data to the P2WDB', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut, 'instantiateWriteLib').returns({
|
||||
postEntry: async () => {
|
||||
return { hash: 'fake-hash' }
|
||||
}
|
||||
})
|
||||
|
||||
const wif = 'L1tcvcqa5PztqqDH4ZEcUmHA9aSHhTau5E2Zwp1xEK5CrKBrjP3m'
|
||||
const inObj = { wif }
|
||||
|
||||
const result = await uut.write(inObj)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result, 'fake-hash')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -84,7 +84,8 @@ describe('#wallet', () => {
|
||||
try {
|
||||
// Force an error
|
||||
uut.WALLET_FILE = ''
|
||||
uut.BchWallet = () => {}
|
||||
uut.BchWallet = () => {
|
||||
}
|
||||
|
||||
await uut.openWallet()
|
||||
// console.log('result: ', result)
|
||||
@@ -120,7 +121,7 @@ describe('#wallet', () => {
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.include(err.message, 'Cannot read property')
|
||||
assert.include(err.message, 'Cannot read')
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -155,44 +156,44 @@ describe('#wallet', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#burnPsf', () => {
|
||||
it('should burn PSF tokens and return the txid', async () => {
|
||||
// mock instance of minimal-slp-wallet
|
||||
uut.bchWallet = new MockBchWallet()
|
||||
|
||||
const result = await uut.burnPsf()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.success, true)
|
||||
assert.equal(result.txid, 'txid')
|
||||
})
|
||||
|
||||
it('should throw error if no PSF tokens are found', async () => {
|
||||
try {
|
||||
// mock instance of minimal-slp-wallet
|
||||
uut.bchWallet = new MockBchWallet()
|
||||
|
||||
// Remove the PSF token from the mock data.
|
||||
uut.bchWallet.utxos.utxoStore.slpUtxos.type1.tokens.pop()
|
||||
|
||||
await uut.burnPsf()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Token UTXO of with ID of')
|
||||
}
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
await uut.burnPsf()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'Cannot read property')
|
||||
}
|
||||
})
|
||||
})
|
||||
// describe('#burnPsf', () => {
|
||||
// it('should burn PSF tokens and return the txid', async () => {
|
||||
// // mock instance of minimal-slp-wallet
|
||||
// uut.bchWallet = new MockBchWallet()
|
||||
//
|
||||
// const result = await uut.burnPsf()
|
||||
// // console.log('result: ', result)
|
||||
//
|
||||
// assert.equal(result.success, true)
|
||||
// assert.equal(result.txid, 'txid')
|
||||
// })
|
||||
//
|
||||
// it('should throw error if no PSF tokens are found', async () => {
|
||||
// try {
|
||||
// // mock instance of minimal-slp-wallet
|
||||
// uut.bchWallet = new MockBchWallet()
|
||||
//
|
||||
// // Remove the PSF token from the mock data.
|
||||
// uut.bchWallet.utxos.utxoStore.slpUtxos.type1.tokens.pop()
|
||||
//
|
||||
// await uut.burnPsf()
|
||||
//
|
||||
// assert.fail('Unexpected code path')
|
||||
// } catch (err) {
|
||||
// assert.include(err.message, 'Token UTXO of with ID of')
|
||||
// }
|
||||
// })
|
||||
//
|
||||
// it('should catch and throw an error', async () => {
|
||||
// try {
|
||||
// await uut.burnPsf()
|
||||
//
|
||||
// assert.fail('Unexpected code path')
|
||||
// } catch (err) {
|
||||
// assert.include(err.message, 'Cannot read')
|
||||
// }
|
||||
// })
|
||||
// })
|
||||
|
||||
describe('#incrementNextAddress', () => {
|
||||
it('should increment the nextAddress property', async () => {
|
||||
|
||||
@@ -333,7 +333,7 @@ describe('#UserRPC', () => {
|
||||
assert.equal(result.endpoint, 'updateUser')
|
||||
assert.equal(result.success, false)
|
||||
assert.equal(result.status, 422)
|
||||
assert.include(result.message, 'Cannot read property')
|
||||
assert.include(result.message, 'Cannot read')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -375,7 +375,7 @@ describe('#UserRPC', () => {
|
||||
assert.equal(result.endpoint, 'getUser')
|
||||
assert.equal(result.success, false)
|
||||
assert.equal(result.status, 422)
|
||||
assert.include(result.message, 'Cannot read property')
|
||||
assert.include(result.message, 'Cannot read')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -14,7 +14,8 @@ let ctx
|
||||
const mockContext = require('../../../../unit/mocks/ctx-mock').context
|
||||
|
||||
describe('Contact', () => {
|
||||
before(async () => {})
|
||||
before(async () => {
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
uut = new ContactController()
|
||||
@@ -36,7 +37,7 @@ describe('Contact', () => {
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'Cannot read property')
|
||||
assert.include(err.message, 'Cannot read')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ describe('#Entry-REST-Controller', () => {
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'Cannot read property')
|
||||
assert.include(err.message, 'Cannot read')
|
||||
}
|
||||
})
|
||||
|
||||
@@ -79,8 +79,7 @@ describe('#Entry-REST-Controller', () => {
|
||||
entry: 'entry',
|
||||
description: 'test',
|
||||
slpAddress: 'simpleledger:qpnty9t0w93fez04h7yzevujpv8pun204qqp0jfafg',
|
||||
signature:
|
||||
'IFytRg6KpvTHCzcW0ZwVhPqdKtRGpoRDcuEb958yIgJFUJlb1F5qPzt/JnlYE7r012BSFj+UT67DZVTU8oNB5vw=',
|
||||
signature: 'IFytRg6KpvTHCzcW0ZwVhPqdKtRGpoRDcuEb958yIgJFUJlb1F5qPzt/JnlYE7r012BSFj+UT67DZVTU8oNB5vw=',
|
||||
category: 'test'
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,8 @@ let ctx
|
||||
const mockContext = require('../../../../unit/mocks/ctx-mock').context
|
||||
|
||||
describe('Logapi', () => {
|
||||
before(async () => {})
|
||||
before(async () => {
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
uut = new LogsApiController()
|
||||
@@ -35,7 +36,7 @@ describe('Logapi', () => {
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'Cannot read property')
|
||||
assert.include(err.message, 'Cannot read')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -69,7 +69,7 @@ describe('#Users-REST-Controller', () => {
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'Cannot read property')
|
||||
assert.include(err.message, 'Cannot read')
|
||||
}
|
||||
})
|
||||
|
||||
@@ -91,9 +91,9 @@ describe('#Users-REST-Controller', () => {
|
||||
assert.property(ctx.response.body, 'user')
|
||||
assert.property(ctx.response.body, 'token')
|
||||
|
||||
// Used by downstream tests.
|
||||
// testUser = ctx.response.body.user
|
||||
// console.log('testUser: ', testUser)
|
||||
// Used by downstream tests.
|
||||
// testUser = ctx.response.body.user
|
||||
// console.log('testUser: ', testUser)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -181,7 +181,7 @@ describe('#Users-REST-Controller', () => {
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'Cannot read property')
|
||||
assert.include(err.message, 'Cannot read')
|
||||
}
|
||||
})
|
||||
|
||||
@@ -223,7 +223,7 @@ describe('#Users-REST-Controller', () => {
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'Cannot read property')
|
||||
assert.include(err.message, 'Cannot read')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -13,7 +13,8 @@ const ipfs = {
|
||||
ipfsCoord: {
|
||||
useCases: {
|
||||
peer: {
|
||||
sendPrivateMessage: () => {}
|
||||
sendPrivateMessage: () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -82,6 +83,18 @@ const localdb = {
|
||||
static find () {}
|
||||
static findOne () {}
|
||||
|
||||
async save () {
|
||||
return {}
|
||||
}
|
||||
},
|
||||
|
||||
Offer: class Offer {
|
||||
constructor (obj) {}
|
||||
|
||||
static findById () {}
|
||||
static find () {}
|
||||
static findOne () {}
|
||||
|
||||
async save () {
|
||||
return {}
|
||||
}
|
||||
@@ -106,8 +119,10 @@ const bch = {
|
||||
// }
|
||||
const { MockBchWallet } = require('./wallet')
|
||||
const wallet = {
|
||||
burnPsf: async () => {},
|
||||
generateSignature: async () => {},
|
||||
burnPsf: async () => {
|
||||
},
|
||||
generateSignature: async () => {
|
||||
},
|
||||
getKeyPair: async () => {
|
||||
return { cashAddress: 'fakeAddr', wif: 'fakeWif', hdIndex: 1 }
|
||||
},
|
||||
@@ -115,7 +130,9 @@ const wallet = {
|
||||
}
|
||||
|
||||
const p2wdb = {
|
||||
write: async () => {}
|
||||
write: async () => {
|
||||
},
|
||||
checkForSufficientFunds: async () => true
|
||||
}
|
||||
|
||||
module.exports = { ipfs, localdb, bch, wallet, p2wdb, bchjs }
|
||||
module.exports = { ipfs, localdb, bch, wallet, p2wdb, bchjs}
|
||||
|
||||
@@ -5,11 +5,9 @@
|
||||
const BCHJS = require('@psf/bch-js')
|
||||
|
||||
const mockWallet = {
|
||||
mnemonic:
|
||||
'course abstract aerobic deer try switch turtle diet fence affair butter top',
|
||||
mnemonic: 'course abstract aerobic deer try switch turtle diet fence affair butter top',
|
||||
privateKey: 'L5D2UAam8tvo3uii5kpgaGyjvVMimdrXu8nWGQSQjuuAix6ji1YQ',
|
||||
publicKey:
|
||||
'0379433ffc401483ade310469953c1cba77c71af904f07c15bde330d7198b4d6dc',
|
||||
publicKey: '0379433ffc401483ade310469953c1cba77c71af904f07c15bde330d7198b4d6dc',
|
||||
cashAddress: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
|
||||
address: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
|
||||
slpAddress: 'simpleledger:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acq9zm7d33',
|
||||
@@ -29,7 +27,8 @@ class MockBchWallet {
|
||||
this.sendTokens = async () => {
|
||||
return 'fakeTxid'
|
||||
}
|
||||
this.getUtxos = async () => {}
|
||||
this.getUtxos = async () => {
|
||||
}
|
||||
|
||||
// Environment variable is used by wallet-balance.unit.js to force an error.
|
||||
if (process.env.NO_UTXO) {
|
||||
@@ -41,8 +40,7 @@ class MockBchWallet {
|
||||
bchUtxos: [
|
||||
{
|
||||
height: 700685,
|
||||
tx_hash:
|
||||
'1fc577caaff5626a8477162581e57bae1b19dc6aa6c10638013c2b1ba14dc654',
|
||||
tx_hash: '1fc577caaff5626a8477162581e57bae1b19dc6aa6c10638013c2b1ba14dc654',
|
||||
tx_pos: 0,
|
||||
value: 1000,
|
||||
txid: '1fc577caaff5626a8477162581e57bae1b19dc6aa6c10638013c2b1ba14dc654',
|
||||
@@ -51,8 +49,7 @@ class MockBchWallet {
|
||||
},
|
||||
{
|
||||
height: 700685,
|
||||
tx_hash:
|
||||
'1fc577caaff5626a8477162581e57bae1b19dc6aa6c10638013c2b1ba14dc654',
|
||||
tx_hash: '1fc577caaff5626a8477162581e57bae1b19dc6aa6c10638013c2b1ba14dc654',
|
||||
tx_pos: 2,
|
||||
value: 19406,
|
||||
txid: '1fc577caaff5626a8477162581e57bae1b19dc6aa6c10638013c2b1ba14dc654',
|
||||
@@ -66,47 +63,61 @@ class MockBchWallet {
|
||||
mintBatons: [],
|
||||
tokens: [
|
||||
{
|
||||
height: 700522,
|
||||
tx_hash:
|
||||
'bb5691b50930816be78dad76d203a1c97ac94c03f6051b2fa0159c71c43aa3d0',
|
||||
tx_pos: 1,
|
||||
value: 546,
|
||||
txid: 'bb5691b50930816be78dad76d203a1c97ac94c03f6051b2fa0159c71c43aa3d0',
|
||||
vout: 1,
|
||||
utxoType: 'token',
|
||||
transactionType: 'send',
|
||||
tokenId:
|
||||
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
||||
tokenTicker: 'TROUT',
|
||||
tokenName: "Trout's test token",
|
||||
tokenDocumentUrl: 'troutsblog.com',
|
||||
tokenDocumentHash: '',
|
||||
decimals: 2,
|
||||
tokenType: 1,
|
||||
isValid: true,
|
||||
tokenQty: '4.25'
|
||||
'height': 717331,
|
||||
'tx_hash': '74889580bb1a5f8c026aa2f55118ac9917df3332f7abae72a70343daa1c29621',
|
||||
'tx_pos': 1,
|
||||
'value': 546,
|
||||
'txid': '74889580bb1a5f8c026aa2f55118ac9917df3332f7abae72a70343daa1c29621',
|
||||
'vout': 1,
|
||||
'isSlp': true,
|
||||
'type': 'token',
|
||||
'qty': '10',
|
||||
'tokenId': '600ee24d0f208aebc2bdd2c4ee1b9acb6d57343561442e8676b5bbea311d5a0f',
|
||||
'address': 'bitcoincash:qqraj35x6l2qyqhjm5l7qlt7z2245ez8l5z3dwkeq5',
|
||||
'ticker': 'FLIPS',
|
||||
'name': 'FLIPS',
|
||||
'documentUri': '',
|
||||
'documentHash': '',
|
||||
'decimals': 1,
|
||||
'qtyStr': '1'
|
||||
},
|
||||
{
|
||||
height: 0,
|
||||
tx_hash:
|
||||
'c0ac066ce6efa1fa4763bf85a91c738e57c12b8765731bd07f0d8f5a55ce582f',
|
||||
tx_pos: 1,
|
||||
value: 546,
|
||||
txid: 'c0ac066ce6efa1fa4763bf85a91c738e57c12b8765731bd07f0d8f5a55ce582f',
|
||||
vout: 1,
|
||||
utxoType: 'token',
|
||||
transactionType: 'send',
|
||||
tokenId:
|
||||
'38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
|
||||
tokenTicker: 'PSF',
|
||||
tokenName: 'Permissionless Software Foundation',
|
||||
tokenDocumentUrl: 'psfoundation.cash',
|
||||
tokenDocumentHash: '',
|
||||
decimals: 8,
|
||||
tokenType: 1,
|
||||
isValid: true,
|
||||
tokenQty: '1',
|
||||
qtyStr: '1'
|
||||
'height': 730597,
|
||||
'tx_hash': '52520faddfafc46b8f8c9548b097f3a3b82a5bf363b5095047b9c5f83247fe36',
|
||||
'tx_pos': 1,
|
||||
'value': 546,
|
||||
'txid': '52520faddfafc46b8f8c9548b097f3a3b82a5bf363b5095047b9c5f83247fe36',
|
||||
'vout': 1,
|
||||
'isSlp': true,
|
||||
'type': 'token',
|
||||
'qty': '34999991',
|
||||
'tokenId': '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0',
|
||||
'address': 'bitcoincash:qqraj35x6l2qyqhjm5l7qlt7z2245ez8l5z3dwkeq5',
|
||||
'ticker': 'PSF',
|
||||
'name': 'Permissionless Software Foundation',
|
||||
'documentUri': 'psfoundation.cash',
|
||||
'documentHash': '',
|
||||
'decimals': 8,
|
||||
'qtyStr': '0.34999991'
|
||||
},
|
||||
{
|
||||
'height': 730597,
|
||||
'tx_hash': '5dc7e7c91382aed1666a51212dfb74050261e12c3c4f62b6b1e57f42d6c51ee1',
|
||||
'tx_pos': 2,
|
||||
'value': 546,
|
||||
'txid': '5dc7e7c91382aed1666a51212dfb74050261e12c3c4f62b6b1e57f42d6c51ee1',
|
||||
'vout': 2,
|
||||
'isSlp': true,
|
||||
'type': 'token',
|
||||
'qty': '18898',
|
||||
'tokenId': 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
||||
'address': 'bitcoincash:qqraj35x6l2qyqhjm5l7qlt7z2245ez8l5z3dwkeq5',
|
||||
'ticker': 'TROUT',
|
||||
'name': "Trout's test token",
|
||||
'documentUri': 'troutsblog.com',
|
||||
'documentHash': '',
|
||||
'decimals': 2,
|
||||
'qtyStr': '188.98'
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -122,4 +133,4 @@ class MockBchWallet {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { MockBchWallet, mockWallet }
|
||||
module.exports = { MockBchWallet, mockWallet}
|
||||
|
||||
@@ -46,60 +46,13 @@ describe('#offer-use-case', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#createOffer', () => {
|
||||
it('should create an offer and return the hash', async () => {
|
||||
const entryObj = {
|
||||
lokadId: 'SWP',
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'token-id',
|
||||
buyOrSell: 'sell',
|
||||
rateInSats: 1000,
|
||||
minSatsToExchange: 1250,
|
||||
numTokens: 1
|
||||
}
|
||||
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.adapters.wallet, 'burnPsf').resolves('fakeTxid')
|
||||
sandbox.stub(uut.offerEntity, 'validate').returns(entryObj)
|
||||
sandbox.stub(uut, 'ensureFunds').resolves()
|
||||
sandbox.stub(uut, 'moveTokens').resolves({ txid: 'fakeTxid', vout: 0 })
|
||||
sandbox.stub(uut.adapters.wallet.bchWallet, 'getUtxos').resolves()
|
||||
sandbox
|
||||
.stub(uut.adapters.wallet, 'generateSignature')
|
||||
.resolves('fakeSignature')
|
||||
sandbox.stub(uut.adapters.p2wdb, 'write').resolves('fakeHash')
|
||||
|
||||
const result = await uut.createOffer(entryObj)
|
||||
console.log('result: ', result)
|
||||
|
||||
assert.isString(result)
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.offerEntity, 'validate')
|
||||
.throws(new Error('test error'))
|
||||
|
||||
await uut.createOffer()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#ensureFunds', () => {
|
||||
it('should return true if wallet has enough funds for a sell order', async () => {
|
||||
const offerEntity = {
|
||||
lokadId: 'SWP',
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId:
|
||||
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
||||
tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
||||
buyOrSell: 'sell',
|
||||
rateInSats: 1000,
|
||||
minSatsToExchange: 0,
|
||||
@@ -123,8 +76,7 @@ describe('#offer-use-case', () => {
|
||||
lokadId: 'SWP',
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId:
|
||||
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
||||
tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
||||
buyOrSell: 'sell',
|
||||
rateInSats: 1000,
|
||||
minSatsToExchange: 0,
|
||||
@@ -141,4 +93,50 @@ describe('#offer-use-case', () => {
|
||||
assert.equal(result.vout, 0)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#createOffer', () => {
|
||||
it('should create an offer and return the hash', async () => {
|
||||
const entryObj = {
|
||||
lokadId: 'SWP',
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'token-id',
|
||||
buyOrSell: 'sell',
|
||||
rateInSats: 1000,
|
||||
minSatsToExchange: 1250,
|
||||
numTokens: 1
|
||||
}
|
||||
|
||||
// Mock dependencies
|
||||
// sandbox.stub(uut.adapters.wallet, 'burnPsf').resolves('fakeTxid')
|
||||
sandbox.stub(uut.offerEntity, 'validate').returns(entryObj)
|
||||
sandbox.stub(uut, 'ensureFunds').resolves()
|
||||
sandbox.stub(uut, 'moveTokens').resolves({ txid: 'fakeTxid', vout: 0, hdIndex: 1 })
|
||||
sandbox.stub(uut.adapters.wallet.bchWallet, 'getUtxos').resolves()
|
||||
// sandbox
|
||||
// .stub(uut.adapters.wallet, 'generateSignature')
|
||||
// .resolves('fakeSignature')
|
||||
sandbox.stub(uut.adapters.p2wdb, 'write').resolves('fakeHash')
|
||||
|
||||
const result = await uut.createOffer(entryObj)
|
||||
console.log('result: ', result)
|
||||
|
||||
assert.isString(result)
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.offerEntity, 'validate')
|
||||
.throws(new Error('test error'))
|
||||
|
||||
await uut.createOffer()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -153,7 +153,7 @@ describe('#users-use-case', () => {
|
||||
await uut.getAllUsers()
|
||||
// console.log(`users: ${JSON.stringify(users, null, 2)}`)
|
||||
|
||||
// assert.isArray(users)
|
||||
// assert.isArray(users)
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
@@ -228,8 +228,8 @@ describe('#users-use-case', () => {
|
||||
// Assert that the expected properties for the user model exist.
|
||||
// assert.property(result, 'type')
|
||||
assert.property(result, '_id')
|
||||
// assert.property(result, 'email')
|
||||
// assert.property(result, 'name')
|
||||
// assert.property(result, 'email')
|
||||
// assert.property(result, 'name')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -241,7 +241,7 @@ describe('#users-use-case', () => {
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, 'Cannot read property')
|
||||
assert.include(err.message, 'Cannot read')
|
||||
}
|
||||
})
|
||||
|
||||
@@ -346,7 +346,7 @@ describe('#users-use-case', () => {
|
||||
// assert.equal(result.name, 'testy tester')
|
||||
// })
|
||||
|
||||
// TODO: verify that an admin can change the type of a user
|
||||
// TODO: verify that an admin can change the type of a user
|
||||
})
|
||||
|
||||
describe('#authUser', () => {
|
||||
@@ -356,9 +356,9 @@ describe('#users-use-case', () => {
|
||||
await uut.authUser('test@test.com', 'password')
|
||||
// console.log('user: ', user)
|
||||
|
||||
// assert.property(user, '_id')
|
||||
// assert.property(user, 'email')
|
||||
// assert.property(user, 'name')
|
||||
// assert.property(user, '_id')
|
||||
// assert.property(user, 'email')
|
||||
// assert.property(user, 'name')
|
||||
})
|
||||
|
||||
it('should throw an error if no user matches the login', async () => {
|
||||
@@ -399,7 +399,7 @@ describe('#users-use-case', () => {
|
||||
assert.fail('Unexpected code path.')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, 'Cannot read property')
|
||||
assert.include(err.message, 'Cannot read')
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user