mirror of
https://github.com/Permissionless-Software-Foundation/bch-dex.git
synced 2026-09-22 09:11:59 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5a0fc31c7a | ||
|
|
4701f245b9 | ||
|
|
daa06a57ed | ||
|
|
b3157a04ed | ||
|
|
3753db46f6 | ||
|
|
b924449d02 | ||
|
|
dd8d992b8a | ||
|
|
4ed657ae28 | ||
|
|
e14633db79 | ||
|
|
6a4e749218 | ||
|
|
7a5036e3aa | ||
|
|
cca10452c0 | ||
|
|
309ea0774d | ||
|
|
c2b68a0787 | ||
|
|
2bb4cd7cae | ||
|
|
9ca581e998 | ||
|
|
02adbd5634 | ||
|
|
da637df44a | ||
|
|
9315e6f166 | ||
|
|
7e965e8415 | ||
|
|
a5916c95f9 | ||
|
|
708f97d107 | ||
|
|
637738bfb9 | ||
|
|
d8e82c5ad2 | ||
|
|
b23acd3cdc | ||
|
|
c71fda38ff | ||
|
|
f99f49d840 | ||
|
|
2edd924c57 | ||
|
|
689c8fd8d9 | ||
|
|
ffd6c452c9 | ||
|
|
fc8e160874 | ||
|
|
d2f83307e6 | ||
|
|
1a09fcd26f | ||
|
|
db54a067f4 | ||
|
|
9fcee20498 | ||
|
|
0a04ee61ef | ||
|
|
7cae06dd65 | ||
|
|
96064ac9a7 | ||
|
|
3690b749fe | ||
|
|
e2c5821dbe | ||
|
|
9967d5145b | ||
|
|
9d16291855 | ||
|
|
1518d1a12b | ||
|
|
6dbf284876 | ||
|
|
d1e96505c8 | ||
|
|
1e80a3159e | ||
|
|
d3df9546a7 | ||
|
|
3422f3146e | ||
|
|
5c4dc21d3e | ||
|
|
b74f460a9a | ||
|
|
6585ceacc7 | ||
|
|
942dbedd44 | ||
|
|
825ede0760 | ||
|
|
5e4540bd84 | ||
|
|
a3b21d7547 | ||
|
|
87ece9d6bb | ||
|
|
125330f5b0 | ||
|
|
6b3fc78fc9 | ||
|
|
3d7d4a57d2 | ||
|
|
abf07005eb | ||
|
|
b68a143071 | ||
|
|
5c25ebeeca | ||
|
|
3b17ba8e8b | ||
|
|
434a4d7bd6 | ||
|
|
dd36ebc7a0 | ||
|
|
f117fe15df | ||
|
|
0333f77f5a | ||
|
|
3c908d1a4d | ||
|
|
eb46a4c130 | ||
|
|
d0efdcfa07 | ||
|
|
62a7fcb16d | ||
|
|
d8addd5b27 | ||
|
|
bf6f1cf62d | ||
|
|
b7df7c47a5 | ||
|
|
ae260db4af | ||
|
|
778098ca12 | ||
|
|
0c4bb9c4a1 | ||
|
|
9cb975656d | ||
|
|
4f6564423e | ||
|
|
54e7e74ae5 | ||
|
|
324c372119 | ||
|
|
da62f1bbe3 | ||
|
|
129047fe88 | ||
|
|
3f2a12f6ec | ||
|
|
c1b687efd1 | ||
|
|
fb182ced58 | ||
|
|
bec7c11e46 | ||
|
|
fb10e10226 | ||
|
|
5b37e47747 | ||
|
|
a3cea782ba | ||
|
|
14ffdae194 | ||
|
|
faf5a83eb6 | ||
|
|
92a35b220f | ||
|
|
58e934eeab | ||
|
|
d5de76729d | ||
|
|
805cac97d9 | ||
|
|
91fa460158 | ||
|
|
2d6055b537 | ||
|
|
72e6a37ed1 | ||
|
|
9d18a24c7e | ||
|
|
feacf6e571 | ||
|
|
c51f7cdf3a | ||
|
|
07fbe8c654 | ||
|
|
a65ec7494f | ||
|
|
59fca7903f | ||
|
|
f8948f12fa | ||
|
|
b79163cdf7 | ||
|
|
2d537994ef | ||
|
|
2ae963ee62 | ||
|
|
f5cd59eb12 | ||
|
|
d4f9e128b3 | ||
|
|
3c322ecbd0 | ||
|
|
56944176fa | ||
|
|
5f9cdc3f2e | ||
|
|
c20938f9f7 | ||
|
|
5be3c92a29 | ||
|
|
1476500c61 | ||
|
|
519980eb99 | ||
|
|
1e4b71aacd | ||
|
|
2c1c009b64 | ||
|
|
09cbfd00e8 | ||
|
|
2c21d79740 | ||
|
|
15eb871b1f | ||
|
|
f4e80017d6 | ||
|
|
e27b9da2ee | ||
|
|
adb210cd60 | ||
|
|
ff7697baf6 | ||
|
|
d65c3701cd | ||
|
|
4303efdb43 | ||
|
|
87b1b7f9ca | ||
|
|
3dddf21986 | ||
|
|
238bee3d92 | ||
|
|
04f77c883a | ||
|
|
cddf141ce4 | ||
|
|
628d8c01f8 | ||
|
|
0ed4a03314 | ||
|
|
5eb8464469 | ||
|
|
d1dc4dd198 | ||
|
|
1ba3633fac | ||
|
|
69dd2c2627 | ||
|
|
0a838b7fd7 | ||
|
|
de9ede9859 | ||
|
|
8273ae29d0 | ||
|
|
4d733f0995 | ||
|
|
f812580169 | ||
|
|
ec8dc91d36 | ||
|
|
03214fd7df | ||
|
|
9aae6da4a7 | ||
|
|
6e7ab40dea | ||
|
|
3b45e3c2c4 | ||
|
|
756002dd8c | ||
|
|
86da41b6c8 | ||
|
|
c2838b6117 | ||
|
|
af33de3f9a | ||
|
|
50d7cf137f | ||
|
|
dad9803d69 | ||
|
|
a235e57302 | ||
|
|
bb2f20b322 | ||
|
|
1f91bb05dd | ||
|
|
5e044f3d10 | ||
|
|
a94f2d32b5 | ||
|
|
4b66305392 | ||
|
|
e23f8c2019 | ||
|
|
f9e76a3dfb | ||
|
|
470fa95b74 | ||
|
|
f2b8ccaa3a | ||
|
|
fa834e9114 | ||
|
|
c45b512fc6 | ||
|
|
e0d1aa24d4 | ||
|
|
a4faecf56e | ||
|
|
95e66efc2f | ||
|
|
881a882175 | ||
|
|
80945039b0 | ||
|
|
e470b26771 | ||
|
|
eb458f1e9f | ||
|
|
21f4bc2f4c | ||
|
|
7e2e28834a | ||
|
|
07551bb693 | ||
|
|
207f00ba81 | ||
|
|
3ccdf40a9c | ||
|
|
0b940c797c | ||
|
|
04f6dbee54 | ||
|
|
632320960f | ||
|
|
b26160b748 | ||
|
|
04f45eea6a | ||
|
|
9096db1614 | ||
|
|
369415bf8d | ||
|
|
7cb536d312 | ||
|
|
7e3ce1564d | ||
|
|
592ea60892 | ||
|
|
d14270c5b0 | ||
|
|
2d9a1d7cbf | ||
|
|
e6c942d532 | ||
|
|
1206b6576e | ||
|
|
dbd5845e3a | ||
|
|
d14dcae6f5 | ||
|
|
6b44aab429 | ||
|
|
8cd8086440 | ||
|
|
37e2377113 | ||
|
|
a6949f0e8d | ||
|
|
8154dd96f9 | ||
|
|
e37223cc88 | ||
|
|
5002ef2b90 | ||
|
|
5d95109ad4 | ||
|
|
13b3150f62 | ||
|
|
b3a4a7c1ab | ||
|
|
733e8fd405 | ||
|
|
baee72c1c6 | ||
|
|
11618a796f | ||
|
|
025ae20704 |
@@ -1,34 +1,75 @@
|
||||
# bch-dex
|
||||
|
||||
[](http://standardjs.com) [](https://github.com/semantic-release/semantic-release)
|
||||
[](http://standardjs.com) [](https://github.com/semantic-release/semantic-release) [](https://gitter.im/Permissionless-Software-Foundation/psf-dex-dev?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
This is a prototype web service that monitors the [P2WDB](https://github.com/Permissionless-Software-Foundation/ipfs-p2wdb-service) for trading signals, to trade BCH and SLP tokens. It's inspired by the [SWaP Prototype](https://github.com/vinarmani/swap-protocol/blob/master/swap-protocol-spec.md).
|
||||
`bch-dex` is a decentralized exchange for permissionless trading of [SLP tokens](https://github.com/simpleledger/slp-specifications/blob/master/slp-token-type-1.md) and [NFTs](https://github.com/simpleledger/slp-specifications/blob/master/slp-nft-1.md). Trading fees are incredibly small, compared to other DEX platforms. The software is based entirely on open protocols and open source software. It's not a '.com' website that you visit, but software you run on your own computer, in the confort of your own home. So long as you have an internet connection and the technical knowledge to run this software, no one can stop you from trading tokens. `bch-dex` software is maintained by the [Permissionless Software Foundation](https://psfoundation.cash/).
|
||||
|
||||
This repository was forked from [ipfs-torlist-service](https://github.com/Permissionless-Software-Foundation/ipfs-torlist-service).
|
||||
- [High-level Overview of bch-dex](https://youtu.be/LVX8CLi4sHw) (Video)
|
||||
|
||||
## Development Notes
|
||||
This is a prototype web service that monitors the [P2WDB](https://github.com/Permissionless-Software-Foundation/ipfs-p2wdb-service) for trading signals, to trade BCH and SLP tokens. It's inspired by the [SWaP Protocol](https://github.com/vinarmani/swap-protocol/blob/master/swap-protocol-spec.md).
|
||||
|
||||
This repository contains the back end code. The user interface is contained in the [bch-dex-ui](https://github.com/Permissionless-Software-Foundation/bch-dex-ui) repository.
|
||||
|
||||
**Warning**: This repository is under active development. Things will be constantly changing and breaking.
|
||||
|
||||
### Road Map:
|
||||
## Media
|
||||
- [High-level Overview of bch-dex](https://youtu.be/LVX8CLi4sHw) (Video)
|
||||
- [Developer walk-through for installing and hacking on bch-dex](https://youtu.be/T5XI43-SWJo) (Video)
|
||||
- This video is out of date. Follow the instructions below.
|
||||
|
||||
- Phase 1: MVP (single user)
|
||||
- Move tokens or BCH for sale to temporary address.
|
||||
- Accept input from user:
|
||||
- Submit Signal message to P2WDB for new buy/sell order.
|
||||
- Check UTXO status before submitting Payment message.
|
||||
- React to webhook when new Offers come in.
|
||||
- Add and track new Offers in MongoDB.
|
||||
- Process Payment signals:
|
||||
- Checking transaction for compatibility
|
||||
- Broadcast/close trades managed by this app.
|
||||
- Phase 2: Build single-user web app
|
||||
- _Make_ buy or sell orders.
|
||||
- Browse Offers on the market.
|
||||
- _Take_ buy or sell orders.
|
||||
- Phase 3: Multi-user
|
||||
- Configure REST API as an IPFS Service that can offer services to multiple users.
|
||||
- Update web app to display and select available SWaP services over IPFS.
|
||||
## Participate
|
||||
This is an open source project, and we encourage other JavaScript developers to participate in its creation and maintenance. We have two chat rooms for the community:
|
||||
- [Telegram Channel](https://t.me/psf_dex_dev)
|
||||
- [Gitter Channel](https://gitter.im/Permissionless-Software-Foundation/psf-dex-dev)
|
||||
|
||||
## Installation
|
||||
Running the DEX requires composition of these different software packages:
|
||||
- bch-dex - This repository is the back end software that tracks trade data on the network, generates [Offers and Counter Offers](https://github.com/Permissionless-Software-Foundation/bch-dex/tree/ct-unstable/dev-docs#definitions), and finalizes trades by accepting Counter Offers.
|
||||
- [bch-dex-ui](https://github.com/Permissionless-Software-Foundation/bch-dex-ui) is a [Gatsby](https://www.gatsbyjs.com/) web app and user interface (UI) for bch-dex.
|
||||
- [P2WDB](https://github.com/Permissionless-Software-Foundation/ipfs-p2wdb-service) is a censorship-resistant database used to communicate trade data between peers running bch-dex.
|
||||
- [IPFS](https://ipfs.io/) is a censorship-resistant network for communicating data over the internet.
|
||||
- [MongoDB](https://www.mongodb.com/) is a database used by both P2WDB and bch-dex to store and manage local data.
|
||||
|
||||
The above software is orchestrated using [Docker](https://www.docker.com/) and Docker Compose. The target operating system is Ubuntu 20+, and the target hardware is amd64 (normal desktop PCs) and the arm64 (Raspberry Pi 4). Trying to operate this software on other operating systems or hardware is possible, but not supported.
|
||||
|
||||
The Raspberry Pi 4 is the primary hardware target, because this software functions best when it is always running. The Pi 4 makes a great appliance for monitoring trade signals and finalizing trades.
|
||||
|
||||
Instructions for setting up Node.js, Docker, and Docker Compose can be found in [this Gist](https://gist.github.com/christroutner/a39f656850dc022b60f25c9663dd1cdd). Walk-through videos can also be found on the [PSF Videos page](https://psfoundation.cash/video/).
|
||||
|
||||
Here are the steps involved in setting up bch-dex:
|
||||
|
||||
1. Follow the direction in [this Gist](https://gist.github.com/christroutner/a39f656850dc022b60f25c9663dd1cdd) to install Node.js, Docker, and Docker Compose.
|
||||
1. Clone the repository with `git clone https://github.com/Permissionless-Software-Foundation/bch-dex` and enter it with `cd bch-dex`.
|
||||
1. Install dependencies by running `npm install`
|
||||
1. Create a wallet:
|
||||
- `cd production/scripts`
|
||||
- `node create-wallet.js`
|
||||
1. Change directory to the `production/docker` or `production/rpi-docker` folder depending on your hardware target.
|
||||
1. Pull the Docker images down from Docker Hub: `docker-compose pull`
|
||||
1. Start the Docker containers with `docker-compose up -d`
|
||||
1. Wait for the P2WDB to sync and populate bch-dex with trade data. You can monitor it with `docker logs --tail 20 -f p2wdb`.
|
||||
1. Open a web browser and navigate the `http://localhost:4500`. You'll be able to see new Offers as they come in and are detected by bch-dex.
|
||||
1. To take the other side of the trade, click the `Take` button in the UI.
|
||||
1. You can add the 12-word mnemonic from the `wallet.json` file to the the web wallet, which will mirror your wallet in the UI, and allow you to perform basic wallet functions (send and receive BCH and tokens).
|
||||
|
||||
## Blockchain Infrastructure
|
||||
|
||||
The P2WDB and bch-dex containers requires a 'Global Back End' (as described in [CashStack.info](https://cashstack.info)) in order to connect to the blockchain. By default, the Docker containers connect to [free-bch.fullstack.cash](https://free-bch.fullstack.cash/). Several community-provided Global Back End servers are provided and can be [viewed here](https://gist.github.com/christroutner/63c5513782181f8b8ea3eb89f7cadeb6). The back end can be changed by setting the `CONSUMER_URL` environment variable in the `docker-compose.yml` file.
|
||||
|
||||
### Applying Software Updates
|
||||
As this is an active project, software updates will happen frequently. To apply a software update, perform these steps.
|
||||
|
||||
|
||||
1. Enter the `docker` or `rpi-docker` folder, depending on your hardware target.
|
||||
1. Bring down the Docker containers with `docker-compose down`.
|
||||
1. Pull in the software updates with `git pull`
|
||||
1. Update dependencies with `npm install`
|
||||
1. Pull new Docker images with `docker-compose pull`
|
||||
1. Clean up disk space by deleting old Docker images with `./cleanup-images.sh`
|
||||
1. Start the Docker containers with `docker-compose up -d`
|
||||
|
||||
|
||||
Sometimes it may be necessary to delete the databases before applying a software update. This can be done by stopping the Docker containers and deleting the `production/data` directory. When the Docker containers are restarted, they will recreate that directory. The P2WDB will re-sync and bch-dex will be populated with fresh trade data.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
+61
-37
@@ -24,39 +24,48 @@ const config = require('../config') // this first.
|
||||
const AdminLib = require('../src/adapters/admin')
|
||||
// const adminLib = new AdminLib()
|
||||
|
||||
const WebHookLib = require('../src/adapters/webhook')
|
||||
const webhookLib = new WebHookLib()
|
||||
// const WebHookLib = require('../src/adapters/webhook')
|
||||
// const webhookLib = new WebHookLib()
|
||||
|
||||
// const JSONRPC = require('../src/rpc')
|
||||
// const rpc = new JSONRPC()
|
||||
|
||||
const errorMiddleware = require('../src/controllers/rest-api/middleware/error')
|
||||
// const { wlogger } = require('../src/adapters/wlogger')
|
||||
const Controllers = require('../src/controllers')
|
||||
|
||||
class Server {
|
||||
constructor () {
|
||||
// Encapsulate dependencies
|
||||
this.adminLib = new AdminLib()
|
||||
this.controllers = new Controllers()
|
||||
this.mongoose = mongoose
|
||||
this.config = config
|
||||
this.process = process
|
||||
}
|
||||
|
||||
async startServer () {
|
||||
try {
|
||||
// Create a Koa instance.
|
||||
const app = new Koa()
|
||||
app.keys = [config.session]
|
||||
app.keys = [this.config.session]
|
||||
|
||||
// Connect to the Mongo Database.
|
||||
mongoose.Promise = global.Promise
|
||||
mongoose.set('useCreateIndex', true) // Stop deprecation warning.
|
||||
this.mongoose.Promise = global.Promise
|
||||
this.mongoose.set('useCreateIndex', true) // Stop deprecation warning.
|
||||
console.log(
|
||||
`Connecting to MongoDB with this connection string: ${config.database}`
|
||||
`Connecting to MongoDB with this connection string: ${this.config.database}`
|
||||
)
|
||||
await mongoose.connect(config.database, {
|
||||
await this.mongoose.connect(this.config.database, {
|
||||
useUnifiedTopology: true,
|
||||
useNewUrlParser: true
|
||||
})
|
||||
|
||||
console.log(`Starting environment: ${config.env}`)
|
||||
console.log(`Debug level: ${config.debugLevel}`)
|
||||
console.log(`Starting environment: ${this.config.env}`)
|
||||
console.log(`Debug level: ${this.config.debugLevel}\n`)
|
||||
console.log(`Using this web3 CashStack service: ${this.config.consumerUrl}`)
|
||||
console.log('Alternative web services listed here: https://gist.github.com/christroutner/63c5513782181f8b8ea3eb89f7cadeb6')
|
||||
console.log('This app is built on top of the CashStack. Find out more: https://CashStack.info\n')
|
||||
|
||||
// MIDDLEWARE START
|
||||
|
||||
@@ -81,33 +90,42 @@ class Server {
|
||||
// Dev Note: This line must come BEFORE controllers.attachRESTControllers()
|
||||
app.use(cors({ origin: '*' }))
|
||||
|
||||
// Attach REST API and JSON RPC controllers to the app.
|
||||
const Controllers = require('../src/controllers')
|
||||
const controllers = new Controllers()
|
||||
await controllers.attachRESTControllers(app)
|
||||
// Wait for any adapters to initialize.
|
||||
await this.controllers.initAdapters()
|
||||
|
||||
app.controllers = controllers
|
||||
// Wait for any use-libraries to initialize.
|
||||
await this.controllers.initUseCases()
|
||||
|
||||
// Attach REST API and JSON RPC controllers to the app.
|
||||
await this.controllers.attachRESTControllers(app)
|
||||
|
||||
app.controllers = this.controllers
|
||||
|
||||
// MIDDLEWARE END
|
||||
|
||||
// 7/7/22 CT: This code paragraph can be deleted
|
||||
// Delay startup to give the P2WDB time to start first, so that it accepts the webook call
|
||||
// if (this.config.env !== 'test') {
|
||||
// await this.sleep(20000)
|
||||
// }
|
||||
// Create webhook
|
||||
try {
|
||||
try {
|
||||
// Delete an old webhook if it exists.
|
||||
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}/p2wdb`)
|
||||
console.log('Webhook created')
|
||||
} catch (error) {
|
||||
console.log('Webhook cant be created')
|
||||
}
|
||||
// try {
|
||||
// try {
|
||||
// // Delete an old webhook if it exists.
|
||||
// await webhookLib.deleteWebhook(config.webhookTarget)
|
||||
// } catch (err) {
|
||||
// /* exit quietly */
|
||||
// // console.log('err deleting webhook: ', err)
|
||||
// }
|
||||
//
|
||||
// await webhookLib.createWebhook(config.webhookTarget)
|
||||
// console.log('Webhook created')
|
||||
// } catch (error) {
|
||||
// console.log('Webhook cant be created')
|
||||
// }
|
||||
|
||||
// startServer()
|
||||
await app.listen(config.port)
|
||||
this.server = await app.listen(config.port)
|
||||
console.log(`Server started on ${config.port}`)
|
||||
|
||||
// Create the system admin user.
|
||||
@@ -116,10 +134,16 @@ class Server {
|
||||
|
||||
// Attach the other IPFS controllers.
|
||||
// Skip if this is a test environment.
|
||||
if (config.env !== 'test') {
|
||||
await controllers.attachControllers(app)
|
||||
if (this.config.env !== 'test') {
|
||||
await this.controllers.attachControllers(app)
|
||||
}
|
||||
|
||||
// Display configuration settings
|
||||
console.log('\nConfiguration:')
|
||||
console.log(`Circuit Relay: ${this.config.isCircuitRelay}`)
|
||||
console.log(`IPFS TCP port: ${this.config.ipfsTcpPort}`)
|
||||
console.log(`IPFS WS port: ${this.config.ipfsWsPort}\n`)
|
||||
|
||||
return app
|
||||
} catch (err) {
|
||||
console.error('Could not start server. Error: ', err)
|
||||
@@ -127,14 +151,14 @@ class Server {
|
||||
console.log(
|
||||
'Exiting after 5 seconds. Depending on process manager to restart.'
|
||||
)
|
||||
await sleep(5000)
|
||||
process.exit(1)
|
||||
await this.sleep(5000)
|
||||
this.process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
sleep (ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||
}
|
||||
}
|
||||
|
||||
function sleep (ms) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
module.exports = Server
|
||||
|
||||
Vendored
+10
-5
@@ -35,8 +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://wa-usa-bch-consumer.fullstackcash.nl',
|
||||
: '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',
|
||||
@@ -85,7 +85,10 @@ module.exports = {
|
||||
webhookService: process.env.WEBHOOKSERVICE
|
||||
? process.env.WEBHOOKSERVICE
|
||||
: 'http://localhost:5010/webhook', // P2WDB.
|
||||
p2wdbAppId: process.env.APP_ID ? process.env.APP_ID : 'bch-dex-test556',
|
||||
p2wdbAppId: process.env.APP_ID ? process.env.APP_ID : 'bch-dex-001',
|
||||
webhookTarget: process.env.WEBHOOKTARGET
|
||||
? process.env.WEBHOOKTARGET
|
||||
: 'http://localhost:5700/p2wdb',
|
||||
|
||||
// IPFS Ports
|
||||
ipfsTcpPort: process.env.IPFS_TCP_PORT ? process.env.IPFS_TCP_PORT : 4001,
|
||||
@@ -94,12 +97,14 @@ module.exports = {
|
||||
// BCH Mnemonic for generating encryption keys and payment address
|
||||
mnemonic: process.env.MNEMONIC ? process.env.MNEMONIC : '',
|
||||
|
||||
debugLevel: process.env.DEBUG_LEVEL ? parseInt(process.env.DEBUG_LEVEL) : 2,
|
||||
debugLevel: process.env.DEBUG_LEVEL ? parseInt(process.env.DEBUG_LEVEL) : 1,
|
||||
|
||||
// Settings for production, using external go-ipfs node.
|
||||
isProduction: process.env.SVC_ENV === 'production' ? true : false,
|
||||
ipfsHost: process.env.IPFS_HOST ? process.env.IPFS_HOST : 'localhost',
|
||||
ipfsApiPort: process.env.IPFS_API_PORT
|
||||
? parseInt(process.env.IPFS_API_PORT)
|
||||
: 5001
|
||||
: 5001,
|
||||
|
||||
chatPubSubChan: 'psf-ipfs-chat-001'
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
const common = require('./env/common')
|
||||
|
||||
const env = process.env.BCH_ENV || 'development'
|
||||
const env = process.env.BCH_DEX || 'development'
|
||||
const config = require(`./env/${env}`)
|
||||
|
||||
module.exports = Object.assign({}, common, config)
|
||||
|
||||
+4
-3
@@ -74,7 +74,7 @@ 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 `/order` REST API endpoint to create a new Order.
|
||||
- `bch-dex` will move the funds into a segregated UTXO, and will use that UTXO to create an Order. The Order data is written to the P2WDB. The Order 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 `/offer` endpoint by the P2WDB. This event will trigger the import of the new data into the apps local Mongo database, and generate a new Offer model.
|
||||
- After submitting the data to the P2WDB, `bch-dex` will receive a webhook call to its POST `/p2wdb` endpoint by the P2WDB. This event will trigger the import of the new data into the apps local Mongo database, and generate a new Offer 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 Offer model.
|
||||
|
||||
## Taking an Offer
|
||||
@@ -82,8 +82,8 @@ Writing data follows these steps:
|
||||
Users can browse the Offers tracked by a local `bch-dex` by using a *Client*. When they find an Offer they want to to take, they'll use some UI element that will send data to the POST `/offer/take` REST API endpoint. These series of steps happen:
|
||||
|
||||
- The `bch-dex` checks to see if the wallet it controls has enough BCH to take the other side of the Offer. If it does, the funds for the offer are moved to a segregated UTXO.
|
||||
- The new UTXO is used to generate a *Counter Offer*, which contain a partially signed transaction saved as a hex string.
|
||||
- The *Counter Offer* is submitted to the P2WDB. This triggers a webhook event in every running instance of `bch-dex` on the network.
|
||||
- The new UTXO is used to generate a *Counter Offer*, which contains a partially signed transaction saved as a hex string.
|
||||
- The *Counter Offer* is uploaded to the P2WDB. This triggers a webhook event in every running instance of `bch-dex` on the network.
|
||||
- When the webhook is triggered, `bch-dex` will check to see if the *Counter Offer* matches an *Order* under its control. If a match is found, it will trigger an *Accept* event.
|
||||
|
||||
## Accepting a Counter Offer
|
||||
@@ -92,6 +92,7 @@ This part of the process is automated and does not require input from the user.
|
||||
|
||||
- When a *Counter Offer* is received that matches an *Order* tracked by the local copy of `bch-dex`, it will trigger the *Acceptance* phase.
|
||||
- In the *Acceptance* phase, the transaction will be checked to see if it matches the conditions in the original *Order*. If all checks pass, `bch-dex` will sign the transaction and broadcast it, completing the trade.
|
||||
- A 'garbage collection' function that runs periodically will delete Orders and Offers in bch-dex that have had their UTXO spent, automatically cleaning up stale trade data that is no longer valid.
|
||||
|
||||
## Maintenance
|
||||
|
||||
|
||||
+68
-38
@@ -26,31 +26,31 @@ Order entities have the following properties:
|
||||
- _utxoTxid_ - The TXID of the UTXO representing the token or BCH being offered for sale.
|
||||
- _utxoVout_ - The vout of the UTXO representing the token or BCH being offered for sale.
|
||||
|
||||
- Trade Data:
|
||||
- _buyOrSell_ - A string with a value `buy` or `sell` indicating which type of offer this is.
|
||||
- _numTokens_ - The maximum number of tokens offered for sale.
|
||||
- _rateInBaseUnit_ - The rate in terms of currency-unit-per-token. Ex: 1000 = 1000 sats per token
|
||||
- For Bitcoin, the min currency is sats.
|
||||
- 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.
|
||||
- Trade Data:
|
||||
- _buyOrSell_ - A string with a value `buy` or `sell` indicating which type of offer this is.
|
||||
- _numTokens_ - The maximum number of tokens offered for sale.
|
||||
- _rateInBaseUnit_ - The rate in terms of currency-unit-per-token. Ex: 1000 = 1000 sats per token
|
||||
- For Bitcoin, the min currency is sats.
|
||||
- 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.
|
||||
- _sigMsg_ - The clear-text message used to generate the signature.
|
||||
- _offerBchAddr_ - The BCH address controlling the offer.
|
||||
- _offerPubKey_ - The public key used to generate the BCH address, used for encryption.
|
||||
- Authentication Data:
|
||||
- _signature_ - A message signed by the address which created the order.
|
||||
- _sigMsg_ - The clear-text message used to generate the signature.
|
||||
- _offerBchAddr_ - The BCH address controlling the offer.
|
||||
- _offerPubKey_ - The public key used to generate the BCH address, used for encryption.
|
||||
|
||||
- Wallet Data:
|
||||
- _hdIndex_ - The HD index of the wallet used to generate the keypair to store the UTXO being offered for sale.
|
||||
- Wallet Data:
|
||||
- _hdIndex_ - The HD index of the wallet used to generate the keypair to store the UTXO being offered for sale.
|
||||
|
||||
- SWaP Protocol properties:
|
||||
- _lokadId_ - Not used. Provided for future functionality.
|
||||
- _messageType_ - Not used. Provided for future functionality.
|
||||
- _messageClass_ - Not used. Provided for future functionality.
|
||||
- SWaP Protocol properties:
|
||||
- _lokadId_ - Not used. Provided for future functionality.
|
||||
- _messageType_ - Not used. Provided for future functionality.
|
||||
- _messageClass_ - Not used. Provided for future functionality.
|
||||
|
||||
|
||||
### Offer
|
||||
@@ -96,46 +96,76 @@ Offer entities have the following properties:
|
||||
- _messageType_ - Not used. Provided for future functionality.
|
||||
- _messageClass_ - Not used. Provided for future functionality.
|
||||
|
||||
When an Offer is uploaded to the P2WDB, the following properties are added to the data:
|
||||
|
||||
- Added properties to P2WDB data:
|
||||
- *dataType* - Has a value of 'offer'. This is used to route the data correctly when the P2WDB webhook passes the data to `bch-dex`.
|
||||
|
||||
### Counter Offer
|
||||
|
||||
A Counter Offer is the other side of an Offer. It contains a partially signed transaction, created by the Taker. The Maker will review the Counter Offer before accepting and finalizing the trade.
|
||||
A Counter Offer is the other side of the trade, a mirror image to an Offer. It contains a partially signed transaction, created by the Taker. The Maker will review the Counter Offer before accepting and finalizing the trade.
|
||||
|
||||
Counter Offers are not tracked via database models like Offers and Orders are. They are processed by `bch-dex` as soon as they are received. A garbage collection function will be called periodically, to sweep the segregated UTXOs used to generate a Counter Offer back into the root address of the wallet, if it is not accepted after a period of time.
|
||||
|
||||
To generate a Counter Offer, a segregated UTXO is created that matches the requirements in the Offer. A partially signed transaction is created that includes the Offer UTXO and the Counter Offer UTXO. The Taker signs the input consuming the Counter Offer UTXO. The partially signed transaction and other trade data is uploaded to P2WDB.
|
||||
|
||||
When the Counter Offer data is uploaded to the P2WDB, the data must have the following properties:
|
||||
|
||||
- *partialTxHex* - A hexidecimal representation of the partially-signed transaction, which includes the UTXO in the Offer and a second UTXO for that matches the conditions in the Offer.
|
||||
- *dataType* - Must have a value of `counter-offer`
|
||||
|
||||
Details TBD.
|
||||
|
||||
## Use Cases
|
||||
|
||||
Use cases are verbs or actions that is done _to_ an Entity or _between_ Entities.
|
||||
|
||||
### Offer
|
||||
|
||||
- **`createOffer()`** - This method is triggered by a webhook from the P2WDB. It will take the data provided by the P2WDB and create a new Order entity in the local database.
|
||||
|
||||
### Order
|
||||
|
||||
- **`createOrder()`** - A macro command that leverages the other functions in this library to create a new Order and submit it to the P2WDB.
|
||||
- **`ensureFunds()`** - Ensure that the wallet has enough BCH and tokens to complete the requested trade.
|
||||
- **`moveTokens()`** - Move the tokens indicated in the order to a temporary holding address. This will generate the UTXO used in the webhook message. This function moves the funds and returns the UTXO information.
|
||||
- **`createOrder()`** - A macro command that leverages `ensureFunds()` and `moveTokens()`, to create a new Order and submit it to the P2WDB.
|
||||
- **`findOrderByHash()`** - Given a P2WDB CID 'hash', this function will return the corresponding Order model associated with that CID.
|
||||
|
||||
### Counter Offer
|
||||
### Offer
|
||||
|
||||
TBD
|
||||
- **`createOffer()`** - This method is triggered by a webhook from the P2WDB. It will take the data provided by the P2WDB and create a new Offer entity in the local database.
|
||||
- **`listOffers()`** - Returns a list of all the active Offers tracked by `bch-dex`.
|
||||
- **`takeOffer()`** - Generate a segregated UTXO and partially signed transaction, then upload the Counter Offer data to the P2WDB.
|
||||
- **`ensureFunds()`** - Ensure the wallet has enough BCH and tokens to make a Counter Offer.
|
||||
- **`findOfferByHash()`** - Given a P2WDB CID 'hash', this function will return the corresponding Offer model associated with that CID.
|
||||
- **`acceptCounterOffer()`** - This function is triggered 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 diligence on the Counter Offer, then signs and broadcasts the transaction to accept the Counter Offer.
|
||||
|
||||
## Controllers
|
||||
|
||||
Controllers are inputs to the system. When a controller is activated, it causes the system to react in some way.
|
||||
|
||||
### Offers
|
||||
|
||||
- **POST /offer** - This POST REST API endpoint will be triggered by a webhook generated from P2WDB. This will notify the `bch-dex` that a new entry has been added to the P2WDB that matches the `appId` of `swap-<chain>`, where `<chain>` has a value of `avax`, `bch`, or `ecash`. It's a new entry that should be evaluated for inclusion in the `ipfs-swap-service` local database.
|
||||
|
||||
### Orders
|
||||
|
||||
- **POST /order** - This POST REST API endpoint can be triggered by the Client or a simple curl call. It passes in the data needed for `bch-dex` to generate and track a new Order, then submit the data to the P2WDB to generate an Offer that is tracked by all other instances of `bch-dex`.
|
||||
- **POST /order** - This POST REST API endpoint can be triggered by the Client or a simple curl call. It passes in the data needed for `bch-dex` to generate and track a new Order, then submits the data to the P2WDB to generate an Offer that is tracked by all other instances of `bch-dex` on the network.
|
||||
|
||||
### Offers
|
||||
|
||||
- **GET /offer/list** - Returns an array of objects, where each object represents an active Offer.
|
||||
|
||||
- **POST /offer/take** - Given the P2WDB CID of an Offer, calling this endpoint will attempt to generate a Counter Offer and upload it to the P2WDB.
|
||||
|
||||
### P2WDB
|
||||
|
||||
- **POST /p2wdb** - This POST REST API endpoint will be called by a webhook generated from the P2WDB. This will notify the `bch-dex` that a new entry has been added to the P2WDB that matches the `appId` of `swap-<chain>`, where `<chain>` has a value of `avax`, `bch`, or `ecash`. It's a new entry that should be evaluated for inclusion in the `bch-dex` local database. Based on the `dataType` property, the data is routed to either create a new Offer or to process a new Counter Offer.
|
||||
|
||||
## Adapters
|
||||
|
||||
Adapters are output libraries so that the business logic doesn't need to know any specific information about the I/O. They are essentially the output of the application.
|
||||
Adapters are outputs. These libraries exist so that the business logic doesn't need to know any specific information about the I/O. They handle the low-level mechanics of the various subsystems.
|
||||
|
||||
- **localdb** - An adapter for the local database (MongoDB).
|
||||
- **ipfs** - An adapter for IPFS and the ipfs-coord library. It allows the app to use the JSON-RPC over IPFS.
|
||||
- **wallet** - Handles low-level wallet tasks.
|
||||
- **`moveTokens()`** - Move the tokens indicated in the order to a temporary holding address. This will generate the segregated UTXOs used in the trade. This function moves the funds and returns the UTXO information.
|
||||
- **`moveBCH()`** - Same idea as `moveTokens()`, but creates a non-SLP segregated UTXO, used when generating a Counter Offer.
|
||||
- **`openWallet()`** - Opens the JSON file containing the wallet data for the app.
|
||||
- **`instanceWallet()`** - Given the wallet data returned by `openWallet()`, it generates an instance of the [minimal-slp-wallet](https://www.npmjs.com/package/minimal-slp-wallet) for generating UTXOs and interacting with the blockchain.
|
||||
- **`incrementNextAddress()`** - Increments the index of the [HD wallet](https://github.com/bitcoinbook/bitcoinbook/blob/develop/ch05.asciidoc#hd_wallets) used to generate keypairs for storing segregated UTXOs.
|
||||
- **`getKeyPair()`** - Generates a keypair from the HD wallet.
|
||||
- **`generateSignature()`** - Generate a cryptographic signature.
|
||||
- **`generatePartialTx()`** - Create a partial transaction as part of a Counter Offer.
|
||||
- **`deseralizeTx()`** - Used only for debugging purposes. Expands a hex transaction into a JSON object.
|
||||
- **`completeTx()`** - Complete the partially signed transaction by signing the first input, then broadcasting the transaction to the network.
|
||||
|
||||
Generated
+7487
-13260
File diff suppressed because it is too large
Load Diff
+13
-20
@@ -27,14 +27,14 @@
|
||||
},
|
||||
"repository": "Permissionless-Software-Foundation/bch-dex",
|
||||
"dependencies": {
|
||||
"@psf/bch-js": "6.2.1",
|
||||
"axios": "0.21.1",
|
||||
"bch-message-lib": "2.1.4",
|
||||
"@psf/bch-js": "6.4.5",
|
||||
"axios": "0.27.2",
|
||||
"bch-message-lib": "2.2.1",
|
||||
"bcryptjs": "2.4.3",
|
||||
"bitcoincashjs-lib": "3.3.3",
|
||||
"glob": "7.1.6",
|
||||
"ipfs": "0.60.0",
|
||||
"ipfs-coord": "7.1.6",
|
||||
"ipfs": "0.62.3",
|
||||
"ipfs-coord": "8.0.5",
|
||||
"ipfs-http-client": "55.0.0",
|
||||
"jsonrpc-lite": "2.2.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
@@ -52,34 +52,27 @@
|
||||
"koa2-ratelimit": "0.9.1",
|
||||
"libp2p": "^0.36.2",
|
||||
"line-reader": "0.4.0",
|
||||
"minimal-slp-wallet": "4.4.5",
|
||||
"minimal-slp-wallet": "5.1.0",
|
||||
"mongoose": "5.13.13",
|
||||
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
|
||||
"nodemailer": "6.4.17",
|
||||
"p2wdb": "1.3.1",
|
||||
"nodemailer": "6.7.5",
|
||||
"p2wdb": "2.2.1",
|
||||
"passport-local": "1.0.0",
|
||||
"public-ip": "^4.0.4",
|
||||
"winston": "3.3.3",
|
||||
"winston-daily-rotate-file": "4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"apidoc": "0.26.0",
|
||||
"bch-token-sweep": "1.5.16",
|
||||
"apidoc": "0.51.1",
|
||||
"bch-token-sweep": "2.1.2",
|
||||
"chai": "4.3.0",
|
||||
"coveralls": "2.11.4",
|
||||
"eslint": "7.19.0",
|
||||
"eslint-config-prettier": "7.2.0",
|
||||
"eslint-config-standard": "16.0.2",
|
||||
"eslint-plugin-node": "11.1.0",
|
||||
"eslint-plugin-prettier": "3.3.1",
|
||||
"eslint-plugin-promise": "^4.3.1",
|
||||
"eslint-plugin-standard": "4.0.0",
|
||||
"husky": "4.3.8",
|
||||
"mocha": "8.4.0",
|
||||
"mocha": "10.0.0",
|
||||
"nyc": "15.1.0",
|
||||
"semantic-release": "17.4.4",
|
||||
"semantic-release": "19.0.3",
|
||||
"sinon": "9.2.4",
|
||||
"standard": "16.0.4",
|
||||
"standard": "17.0.0",
|
||||
"uuid": "8.3.2"
|
||||
},
|
||||
"release": {
|
||||
|
||||
@@ -1,61 +0,0 @@
|
||||
# Docker Containers
|
||||
|
||||
The 'production' environment is assumed to be a set of Docker containers orchestrated with Docker Compose. The files in this directory will stand up three Docker containers:
|
||||
|
||||
1 An instance of go-ipfs.
|
||||
2 An instance of MongoDB.
|
||||
3 The JavaScript software in this repository.
|
||||
|
||||
The software in this repository depends on the first two containers, so if they aren't running correctly, the application won't run correctly either.
|
||||
|
||||
## IPFS
|
||||
|
||||
IPFS can be a little tricky to set up. By default, the container uses the following ports:
|
||||
|
||||
- 4001 for TCP connections, exposed publicly.
|
||||
- 5001 for control by the application, exposed privately.
|
||||
- 8080 for an IPFS gateway, consumed by the application, exposed privately.
|
||||
|
||||
If you already have an IPFS node running on a the computer, you will need to change the ports to avaid a conflict. To change the ports from the default, you'll need to perform a series of steps, and the order of the steps matter.
|
||||
|
||||
1. Edit the `docker-compose.yml` file and change the ports. Then save the file. Here is an example:
|
||||
|
||||
```
|
||||
ports:
|
||||
- 4101:4101
|
||||
- 172.17.0.1:5101:5101
|
||||
- 172.17.0.1:8180:8180
|
||||
```
|
||||
|
||||
2. Bring the Docker containers up, and then back down. This will allow the IPFS container to create the config file that you'll need to edit.
|
||||
|
||||
- `docker-compose up -d`
|
||||
- Wait a few seconds.
|
||||
- `docker-compose down`
|
||||
|
||||
3. Update the generated config file at `../data/go-ipfs/data/config`, to update the ports in the config file, like this:
|
||||
|
||||
```
|
||||
"Addresses": {
|
||||
"API": "/ip4/0.0.0.0/tcp/5101",
|
||||
"Announce": [],
|
||||
"AppendAnnounce": [],
|
||||
"Gateway": "/ip4/0.0.0.0/tcp/8180",
|
||||
"NoAnnounce": [],
|
||||
"Swarm": [
|
||||
"/ip4/0.0.0.0/tcp/4101",
|
||||
"/ip6/::/tcp/4101",
|
||||
"/ip4/0.0.0.0/udp/4101/quic",
|
||||
"/ip6/::/udp/4101/quic"
|
||||
]
|
||||
},
|
||||
|
||||
```
|
||||
|
||||
4. Update the port changes in the `start-production.sh` shell script. This tells the application which ports to use, in order to control the IPFS node, are are used when signaling other nodes.
|
||||
|
||||
5. Quickly rebuild the containers, to add the modified `start-production.sh` shell script to the application Docker container:
|
||||
|
||||
- `docker-compose build`
|
||||
|
||||
6. Now start the containers, and the port changes to IPFS should be complete.
|
||||
@@ -0,0 +1,73 @@
|
||||
# Create a Dockerized API server
|
||||
#
|
||||
|
||||
#IMAGE BUILD COMMANDS
|
||||
# ct-base-ubuntu = ubuntu 18.04 + nodejs v10 LTS
|
||||
#FROM christroutner/ct-base-ubuntu
|
||||
FROM ubuntu:20.04 as builder
|
||||
MAINTAINER Chris Troutner <chris.troutner@gmail.com>
|
||||
|
||||
#Update the OS and install any OS packages needed.
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y sudo git curl nano gnupg wget
|
||||
|
||||
#Install Node and NPM
|
||||
RUN curl -sL https://deb.nodesource.com/setup_16.x -o nodesource_setup.sh
|
||||
RUN bash nodesource_setup.sh
|
||||
RUN apt-get install -y nodejs build-essential
|
||||
|
||||
#Create the user 'safeuser' and add them to the sudo group.
|
||||
#RUN useradd -ms /bin/bash safeuser
|
||||
#RUN adduser safeuser sudo
|
||||
|
||||
#Set password to 'password' change value below if you want a different password
|
||||
#RUN echo safeuser:password | chpasswd
|
||||
|
||||
#Set the working directory to be the home directory
|
||||
WORKDIR /home/safeuser
|
||||
|
||||
#Setup NPM for non-root global install
|
||||
#RUN mkdir /home/safeuser/.npm-global
|
||||
#RUN chown -R safeuser .npm-global
|
||||
#RUN echo "export PATH=~/.npm-global/bin:$PATH" >> /home/safeuser/.profile
|
||||
#RUN runuser -l safeuser -c "npm config set prefix '~/.npm-global'"
|
||||
|
||||
# Update to the latest version of npm.
|
||||
#RUN npm install -g npm@7.23.0
|
||||
RUN npm install -g npm
|
||||
|
||||
# npm mirror to prevent direct dependency on npm.
|
||||
#RUN npm set registry http://94.130.170.209:4873/
|
||||
|
||||
# Switch to user account.
|
||||
#USER safeuser
|
||||
# Prep 'sudo' commands.
|
||||
#RUN echo 'abcd8765' | sudo -S pwd
|
||||
|
||||
# Clone the rest.bitcoin.com repository
|
||||
WORKDIR /home/safeuser
|
||||
RUN git clone https://github.com/Permissionless-Software-Foundation/bch-dex-ui-v2
|
||||
|
||||
# Switch to the desired branch. `master` is usually stable,
|
||||
# and `stage` has the most up-to-date changes.
|
||||
WORKDIR /home/safeuser/bch-dex-ui-v2
|
||||
|
||||
# For development: switch to unstable branch
|
||||
RUN git checkout ct-unstable
|
||||
|
||||
# Install dependencies
|
||||
#RUN mkdir .ipfsdata
|
||||
#RUN npm install -g @mapbox/node-pre-gyp
|
||||
RUN npm install
|
||||
|
||||
# Build the site
|
||||
RUN CI=true npm run build
|
||||
|
||||
# Load the NGINX image.
|
||||
FROM nginx
|
||||
EXPOSE 80
|
||||
|
||||
# Copy the files built in the first container to the new NGINX container.
|
||||
COPY --from=builder /home/safeuser/bch-dex-ui-v2/build /usr/share/nginx/html
|
||||
|
||||
#USER safeuser
|
||||
@@ -12,7 +12,7 @@ RUN apt-get update
|
||||
RUN apt-get install -y sudo git curl nano gnupg wget
|
||||
|
||||
#Install Node and NPM
|
||||
RUN curl -sL https://deb.nodesource.com/setup_14.x -o nodesource_setup.sh
|
||||
RUN curl -sL https://deb.nodesource.com/setup_16.x -o nodesource_setup.sh
|
||||
RUN bash nodesource_setup.sh
|
||||
RUN apt-get install -y nodejs build-essential
|
||||
|
||||
@@ -46,11 +46,11 @@ RUN npm set registry http://94.130.170.209:4873/
|
||||
|
||||
# Clone the rest.bitcoin.com repository
|
||||
WORKDIR /home/safeuser
|
||||
RUN git clone https://github.com/Permissionless-Software-Foundation/ipfs-service-provider
|
||||
RUN git clone https://github.com/Permissionless-Software-Foundation/bch-dex
|
||||
|
||||
# Switch to the desired branch. `master` is usually stable,
|
||||
# and `stage` has the most up-to-date changes.
|
||||
WORKDIR /home/safeuser/ipfs-service-provider
|
||||
WORKDIR /home/safeuser/bch-dex
|
||||
|
||||
# For development: switch to unstable branch
|
||||
RUN git checkout ct-unstable
|
||||
@@ -63,13 +63,16 @@ RUN npm install
|
||||
# Generate the API docs
|
||||
RUN npm run docs
|
||||
|
||||
VOLUME /home/safeuser/keys
|
||||
# Copy the wallet files
|
||||
#COPY wallet.json wallet.json
|
||||
|
||||
#VOLUME /home/safeuser/keys
|
||||
|
||||
# Expose the port the API will be served on.
|
||||
EXPOSE 5010
|
||||
#EXPOSE 5010
|
||||
|
||||
# Start the application.
|
||||
COPY start-production.sh start-production.sh
|
||||
#COPY start-production.sh start-production.sh
|
||||
CMD ["./start-production.sh"]
|
||||
|
||||
#CMD ["npm", "start"]
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
|
||||
# BEGIN: Optional configuration settings
|
||||
|
||||
# The human readable name this IPFS node identifies as.
|
||||
export COORD_NAME=bch-dex-generic
|
||||
|
||||
# Allow this node to function as a circuit relay. It must not be behind a firewall.
|
||||
#export ENABLE_CIRCUIT_RELAY=true
|
||||
# For browsers to use your circuit realy, you must set up a domain, SSL certificate,
|
||||
# and you must forward that subdomain to the IPFS_WS_PORT.
|
||||
#export CR_DOMAIN=subdomain.yourdomain.com
|
||||
|
||||
# Debug level. 0 = minimal info. 2 = max info.
|
||||
export DEBUG_LEVEL=1
|
||||
|
||||
# END: Optional configuration settings
|
||||
|
||||
|
||||
# Production database connection string.
|
||||
export DBURL=mongodb://172.17.0.1:5666/bch-swap-service-prod
|
||||
|
||||
# Configure REST API port
|
||||
export PORT=5700
|
||||
|
||||
# bch-dex specific env vars
|
||||
export BCH_DEX=production
|
||||
export WEBHOOKSERVICE=http://172.17.0.1:5667/webhook
|
||||
export WEBHOOKTARGET=http://172.17.0.1:5700/p2wdb
|
||||
export APP_ID=bch-dex-001
|
||||
|
||||
npm start
|
||||
@@ -3,21 +3,27 @@
|
||||
version: '3.9'
|
||||
|
||||
services:
|
||||
mongo-ipfs-service:
|
||||
image: mongo
|
||||
container_name: mongo-ipfs-service
|
||||
mongo-p2wdb-service:
|
||||
image: mongo:4.2.0
|
||||
container_name: mongo-p2wdb-service
|
||||
ports:
|
||||
- '5555:27017' # <host port>:<container port>
|
||||
- '5666:27017' # <host port>:<container port>
|
||||
volumes:
|
||||
- ../data/database:/data/db
|
||||
command: mongod --logpath=/dev/null # -- quiet
|
||||
restart: always
|
||||
|
||||
ipfs:
|
||||
image: ipfs/go-ipfs:v0.11.0
|
||||
# https://github.com/christroutner/trickle-ipfs
|
||||
image: christroutner/trickle-ipfs:v1.0.1
|
||||
#build:
|
||||
# context: ./
|
||||
# dockerfile: Dockerfile
|
||||
container_name: ipfs
|
||||
environment:
|
||||
MY_ENV_VAR: 'placeholder'
|
||||
IPFS_DAEMON_ARGUMENTS: '--enable-pubsub-experiment --migrate=true --agent-version-suffix=docker --routing=dhtclient'
|
||||
UPLOAD_KBPS: '1000'
|
||||
DOWNLOAD_KBPS: '1000'
|
||||
logging:
|
||||
driver: 'json-file'
|
||||
options:
|
||||
@@ -26,25 +32,75 @@ services:
|
||||
mem_limit: 2000mb
|
||||
ports:
|
||||
- 4001:4001
|
||||
- 4003:4003
|
||||
- 172.17.0.1:5001:5001
|
||||
- 172.17.0.1:8080:8080
|
||||
command: [
|
||||
'./start-ipfs.sh'
|
||||
]
|
||||
volumes:
|
||||
- ../data/go-ipfs/data:/data/ipfs
|
||||
- ../data/go-ipfs/export:/export
|
||||
# https://docs.docker.com/compose/compose-file/compose-file-v3/#command
|
||||
# https://github.com/ipfs/go-ipfs/blob/91c52657166bcf86f2476926e4fe56694dc26562/Dockerfile#L115
|
||||
command:
|
||||
[
|
||||
'daemon',
|
||||
'--migrate=true',
|
||||
'--agent-version-suffix=docker',
|
||||
'--enable-pubsub-experiment'
|
||||
]
|
||||
- ../data/go-ipfs/data:/root/.ipfs
|
||||
restart: always
|
||||
|
||||
ipfs-service:
|
||||
build: .
|
||||
container_name: ipfs-service
|
||||
p2wdb:
|
||||
#build:
|
||||
# context: ./p2wdb/
|
||||
# dockerfile: Dockerfile
|
||||
image: christroutner/p2wdb:v3.0.9
|
||||
container_name: p2wdb
|
||||
environment:
|
||||
CONSUMER_URL: 'https://free-bch.fullstack.cash'
|
||||
DEBUG_LEVEL: 1
|
||||
logging:
|
||||
driver: 'json-file'
|
||||
options:
|
||||
max-size: '10m'
|
||||
max-file: '10'
|
||||
mem_limit: 1gb
|
||||
links:
|
||||
- mongo-p2wdb-service
|
||||
ports:
|
||||
# <host port>:<container port>
|
||||
- 5667:5667 # REST API
|
||||
#- 5668:5668 # IPFS TCP port
|
||||
#- 5669:5669 # IPFS WS Port
|
||||
volumes:
|
||||
- ../data/ipfsdata/p2wdb:/home/safeuser/ipfs-p2wdb-service/.ipfsdata/p2wdb
|
||||
- ./p2wdb/start-production.sh:/home/safeuser/ipfs-p2wdb-service/start-production.sh
|
||||
restart: always
|
||||
|
||||
bch-dex:
|
||||
#build:
|
||||
# context: ./bch-dex/
|
||||
# dockerfile: Dockerfile
|
||||
image: christroutner/bch-dex:v1.9.6
|
||||
container_name: bch-dex
|
||||
environment:
|
||||
CONSUMER_URL: 'https://free-bch.fullstack.cash'
|
||||
logging:
|
||||
driver: 'json-file'
|
||||
options:
|
||||
max-size: '10m'
|
||||
max-file: '10'
|
||||
mem_limit: 1000mb
|
||||
links:
|
||||
- mongo-p2wdb-service
|
||||
- p2wdb
|
||||
ports:
|
||||
- '5700:5700' # <host port>:<container port>
|
||||
volumes:
|
||||
- ../scripts/wallet.json:/home/safeuser/bch-dex/wallet.json
|
||||
- ./bch-dex/start-production.sh:/home/safeuser/bch-dex/start-production.sh
|
||||
restart: always
|
||||
|
||||
dex-ui:
|
||||
#build:
|
||||
# context: ./bch-dex-ui/
|
||||
# dockerfile: Dockerfile
|
||||
image: christroutner/bch-dex-ui:v1.6.6
|
||||
container_name: dex-ui
|
||||
environment:
|
||||
SERVER: 'http://192.168.2.3'
|
||||
logging:
|
||||
driver: 'json-file'
|
||||
options:
|
||||
@@ -52,10 +108,9 @@ services:
|
||||
max-file: '10'
|
||||
mem_limit: 500mb
|
||||
links:
|
||||
- mongo-ipfs-service
|
||||
- ipfs
|
||||
- bch-dex
|
||||
ports:
|
||||
- '5010:5010' # <host port>:<container port>
|
||||
volumes:
|
||||
- ../data/ipfsdata:/home/safeuser/ipfs-service-provider/.ipfsdata
|
||||
- '4500:80' # <host port>:<container port>
|
||||
# volumes:
|
||||
# - ../data/ipfsdata:/home/safeuser/ipfs-service-provider/.ipfsdata
|
||||
restart: always
|
||||
|
||||
@@ -0,0 +1,71 @@
|
||||
# Create a Docker container for the P2WDB
|
||||
#
|
||||
|
||||
#IMAGE BUILD COMMANDS
|
||||
FROM ubuntu:20.04
|
||||
MAINTAINER Chris Troutner <chris.troutner@gmail.com>
|
||||
|
||||
#Update the OS and install any OS packages needed.
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y sudo git curl nano gnupg wget
|
||||
|
||||
#Install Node and NPM
|
||||
RUN curl -sL https://deb.nodesource.com/setup_16.x -o nodesource_setup.sh
|
||||
RUN bash nodesource_setup.sh
|
||||
RUN apt-get install -y nodejs build-essential
|
||||
|
||||
#Create the user 'safeuser' and add them to the sudo group.
|
||||
RUN useradd -ms /bin/bash safeuser
|
||||
RUN adduser safeuser sudo
|
||||
|
||||
#Set password to 'abcd8765' change value below if you want a different password
|
||||
RUN echo safeuser:abcd8765 | chpasswd
|
||||
|
||||
#Set the working directory to be the users home directory
|
||||
WORKDIR /home/safeuser
|
||||
|
||||
#Setup NPM for non-root global install (like on a mac)
|
||||
RUN mkdir /home/safeuser/.npm-global
|
||||
RUN chown -R safeuser .npm-global
|
||||
RUN echo "export PATH=~/.npm-global/bin:$PATH" >> /home/safeuser/.profile
|
||||
RUN runuser -l safeuser -c "npm config set prefix '~/.npm-global'"
|
||||
|
||||
# Update to the latest version of npm.
|
||||
#RUN npm install -g npm@7.23.0
|
||||
RUN npm install -g npm
|
||||
|
||||
# npm mirror to prevent direct dependency on npm.
|
||||
RUN npm set registry http://94.130.170.209:4873/
|
||||
|
||||
# Switch to user account.
|
||||
#USER safeuser
|
||||
# Prep 'sudo' commands.
|
||||
#RUN echo 'abcd8765' | sudo -S pwd
|
||||
|
||||
# Clone the rest.bitcoin.com repository
|
||||
RUN git clone https://github.com/Permissionless-Software-Foundation/ipfs-p2wdb-service
|
||||
WORKDIR /home/safeuser/ipfs-p2wdb-service
|
||||
RUN mkdir .ipfsdata
|
||||
RUN git checkout ct-unstable
|
||||
|
||||
#RUN npm install fsevents
|
||||
|
||||
# Install dependencies
|
||||
#RUN mkdir .ipfsdata
|
||||
RUN npm install
|
||||
|
||||
# Generate the API docs
|
||||
RUN npm run docs
|
||||
|
||||
# Expose the port the API will be served on.
|
||||
#EXPOSE 5010
|
||||
|
||||
# Start the application.
|
||||
#COPY start-production.sh start-production.sh
|
||||
CMD ["./start-production.sh"]
|
||||
|
||||
#CMD ["npm", "start"]
|
||||
|
||||
# Used for debugging
|
||||
#COPY dummyapp.js dummyapp.js
|
||||
#CMD ["node", "dummyapp.js"]
|
||||
@@ -0,0 +1,4 @@
|
||||
setInterval(function () {
|
||||
const now = new Date()
|
||||
console.log(`ping ${now.toLocaleString()}`)
|
||||
}, 10000)
|
||||
+18
-9
@@ -4,11 +4,10 @@
|
||||
|
||||
# This mnemonic is used to set up persistent public key for e2ee
|
||||
# Replace this with your own 12-word mnemonic.
|
||||
# You can get one at https://wallet.fullstack.cash.
|
||||
export MNEMONIC="olive two muscle bottom coral ancient wait legend bronze useful process session"
|
||||
export MNEMONIC="mass response fiscal world message exact series swallow forward confirm canoe festival"
|
||||
|
||||
# The human readable name this IPFS node identifies as.
|
||||
export COORD_NAME=ipfs-service-provider-generic
|
||||
export COORD_NAME=generic-p2wdb-production
|
||||
|
||||
# Allow this node to function as a circuit relay. It must not be behind a firewall.
|
||||
#export ENABLE_CIRCUIT_RELAY=true
|
||||
@@ -17,22 +16,32 @@ export COORD_NAME=ipfs-service-provider-generic
|
||||
#export CR_DOMAIN=subdomain.yourdomain.com
|
||||
|
||||
# Debug level. 0 = minimal info. 2 = max info.
|
||||
export DEBUG_LEVEL=2
|
||||
# This value set by docker-compose.yml
|
||||
#export DEBUG_LEVEL=1
|
||||
|
||||
# Log-in information for retrieving a JWT token from FullStack.cash.
|
||||
export FULLSTACKLOGIN=demo@demo.com
|
||||
export FULLSTACKPASS=demo
|
||||
|
||||
# END: Optional configuration settings
|
||||
|
||||
|
||||
# Production database connection string.
|
||||
export DBURL=mongodb://172.17.0.1:5555/ipfs-service-prod
|
||||
export DBURL=mongodb://172.17.0.1:5666/p2wdb-service-dev
|
||||
|
||||
# Configure IPFS ports
|
||||
#export IPFS_TCP_PORT=5668
|
||||
#export IPFS_WS_PORT=5669
|
||||
|
||||
# Configure REST API port
|
||||
export PORT=5010
|
||||
export PORT=5667
|
||||
|
||||
# Production settings using external go-ipfs node.
|
||||
export SVC_ENV=production
|
||||
export P2W_ENV=production
|
||||
export IPFS_HOST=172.17.0.1
|
||||
export IPFS_API_PORT=5001
|
||||
export IPFS_TCP_PORT=4001
|
||||
#export IPFS_WS_PORT=5269
|
||||
|
||||
# P2WDB specific env vars
|
||||
#export ORBITDB_NAME=/orbitdb/zdpuAm4fPafpjs5mgbBUJV8Az7kd8Xj1ttD6M1JaAxF8RMRrw/psf-bch-p2wdb-keyvalue-v1.0.0-0004
|
||||
|
||||
npm start
|
||||
@@ -0,0 +1,73 @@
|
||||
# Create a Dockerized API server
|
||||
#
|
||||
|
||||
#IMAGE BUILD COMMANDS
|
||||
# ct-base-ubuntu = ubuntu 18.04 + nodejs v10 LTS
|
||||
#FROM christroutner/ct-base-ubuntu
|
||||
FROM ubuntu:20.04 as builder
|
||||
MAINTAINER Chris Troutner <chris.troutner@gmail.com>
|
||||
|
||||
#Update the OS and install any OS packages needed.
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y sudo git curl nano gnupg wget
|
||||
|
||||
#Install Node and NPM
|
||||
RUN curl -sL https://deb.nodesource.com/setup_16.x -o nodesource_setup.sh
|
||||
RUN bash nodesource_setup.sh
|
||||
RUN apt-get install -y nodejs build-essential
|
||||
|
||||
#Create the user 'safeuser' and add them to the sudo group.
|
||||
#RUN useradd -ms /bin/bash safeuser
|
||||
#RUN adduser safeuser sudo
|
||||
|
||||
#Set password to 'password' change value below if you want a different password
|
||||
#RUN echo safeuser:password | chpasswd
|
||||
|
||||
#Set the working directory to be the home directory
|
||||
WORKDIR /home/safeuser
|
||||
|
||||
#Setup NPM for non-root global install
|
||||
#RUN mkdir /home/safeuser/.npm-global
|
||||
#RUN chown -R safeuser .npm-global
|
||||
#RUN echo "export PATH=~/.npm-global/bin:$PATH" >> /home/safeuser/.profile
|
||||
#RUN runuser -l safeuser -c "npm config set prefix '~/.npm-global'"
|
||||
|
||||
# Update to the latest version of npm.
|
||||
#RUN npm install -g npm@7.23.0
|
||||
RUN npm install -g npm
|
||||
|
||||
# npm mirror to prevent direct dependency on npm.
|
||||
RUN npm set registry http://94.130.170.209:4873/
|
||||
|
||||
# Switch to user account.
|
||||
#USER safeuser
|
||||
# Prep 'sudo' commands.
|
||||
#RUN echo 'abcd8765' | sudo -S pwd
|
||||
|
||||
# Clone the rest.bitcoin.com repository
|
||||
WORKDIR /home/safeuser
|
||||
RUN git clone https://github.com/Permissionless-Software-Foundation/bch-dex-ui-v2
|
||||
|
||||
# Switch to the desired branch. `master` is usually stable,
|
||||
# and `stage` has the most up-to-date changes.
|
||||
WORKDIR /home/safeuser/bch-dex-ui-v2
|
||||
|
||||
# For development: switch to unstable branch
|
||||
RUN git checkout ct-unstable
|
||||
|
||||
# Install dependencies
|
||||
#RUN mkdir .ipfsdata
|
||||
#RUN npm install -g @mapbox/node-pre-gyp
|
||||
RUN npm install
|
||||
|
||||
# Build the site
|
||||
RUN CI=true npm run build
|
||||
|
||||
# Load the NGINX image.
|
||||
FROM nginx
|
||||
EXPOSE 80
|
||||
|
||||
# Copy the files built in the first container to the new NGINX container.
|
||||
COPY --from=builder /home/safeuser/bch-dex-ui-v2/build /usr/share/nginx/html
|
||||
|
||||
#USER safeuser
|
||||
@@ -0,0 +1,78 @@
|
||||
# Create a Dockerized API server
|
||||
#
|
||||
|
||||
#IMAGE BUILD COMMANDS
|
||||
# ct-base-ubuntu = ubuntu 18.04 + nodejs v10 LTS
|
||||
#FROM christroutner/ct-base-ubuntu
|
||||
FROM ubuntu:20.04
|
||||
MAINTAINER Chris Troutner <chris.troutner@gmail.com>
|
||||
|
||||
#Update the OS and install any OS packages needed.
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y sudo git curl nano gnupg wget
|
||||
|
||||
#Install Node and NPM
|
||||
RUN curl -sL https://deb.nodesource.com/setup_16.x -o nodesource_setup.sh
|
||||
RUN bash nodesource_setup.sh
|
||||
RUN apt-get install -y nodejs build-essential
|
||||
|
||||
#Create the user 'safeuser' and add them to the sudo group.
|
||||
#RUN useradd -ms /bin/bash safeuser
|
||||
#RUN adduser safeuser sudo
|
||||
|
||||
#Set password to 'password' change value below if you want a different password
|
||||
#RUN echo safeuser:password | chpasswd
|
||||
|
||||
#Set the working directory to be the home directory
|
||||
WORKDIR /home/safeuser
|
||||
|
||||
#Setup NPM for non-root global install
|
||||
#RUN mkdir /home/safeuser/.npm-global
|
||||
#RUN chown -R safeuser .npm-global
|
||||
#RUN echo "export PATH=~/.npm-global/bin:$PATH" >> /home/safeuser/.profile
|
||||
#RUN runuser -l safeuser -c "npm config set prefix '~/.npm-global'"
|
||||
|
||||
# Update to the latest version of npm.
|
||||
#RUN npm install -g npm@7.23.0
|
||||
RUN npm install -g npm
|
||||
|
||||
# npm mirror to prevent direct dependency on npm.
|
||||
RUN npm set registry http://94.130.170.209:4873/
|
||||
|
||||
# Switch to user account.
|
||||
#USER safeuser
|
||||
# Prep 'sudo' commands.
|
||||
#RUN echo 'abcd8765' | sudo -S pwd
|
||||
|
||||
# Clone the rest.bitcoin.com repository
|
||||
WORKDIR /home/safeuser
|
||||
RUN git clone https://github.com/Permissionless-Software-Foundation/bch-dex
|
||||
|
||||
# Switch to the desired branch. `master` is usually stable,
|
||||
# and `stage` has the most up-to-date changes.
|
||||
WORKDIR /home/safeuser/bch-dex
|
||||
|
||||
# For development: switch to unstable branch
|
||||
RUN git checkout ct-unstable
|
||||
|
||||
# Install dependencies
|
||||
#RUN mkdir .ipfsdata
|
||||
#RUN npm install -g @mapbox/node-pre-gyp
|
||||
RUN npm install
|
||||
|
||||
# Generate the API docs
|
||||
RUN npm run docs
|
||||
|
||||
# Copy the wallet files
|
||||
#COPY wallet.json wallet.json
|
||||
|
||||
#VOLUME /home/safeuser/keys
|
||||
|
||||
# Expose the port the API will be served on.
|
||||
#EXPOSE 5010
|
||||
|
||||
# Start the application.
|
||||
#COPY start-production.sh start-production.sh
|
||||
CMD ["./start-production.sh"]
|
||||
|
||||
#CMD ["npm", "start"]
|
||||
+32
@@ -0,0 +1,32 @@
|
||||
#!/bin/bash
|
||||
|
||||
# BEGIN: Optional configuration settings
|
||||
|
||||
# The human readable name this IPFS node identifies as.
|
||||
export COORD_NAME=bch-dex-generic
|
||||
|
||||
# Allow this node to function as a circuit relay. It must not be behind a firewall.
|
||||
#export ENABLE_CIRCUIT_RELAY=true
|
||||
# For browsers to use your circuit realy, you must set up a domain, SSL certificate,
|
||||
# and you must forward that subdomain to the IPFS_WS_PORT.
|
||||
#export CR_DOMAIN=subdomain.yourdomain.com
|
||||
|
||||
# Debug level. 0 = minimal info. 2 = max info.
|
||||
export DEBUG_LEVEL=1
|
||||
|
||||
# END: Optional configuration settings
|
||||
|
||||
|
||||
# Production database connection string.
|
||||
export DBURL=mongodb://172.17.0.1:5666/bch-swap-service-prod
|
||||
|
||||
# Configure REST API port
|
||||
export PORT=5700
|
||||
|
||||
# bch-dex specific env vars
|
||||
export BCH_DEX=production
|
||||
export WEBHOOKSERVICE=http://172.17.0.1:5667/webhook
|
||||
export WEBHOOKTARGET=http://172.17.0.1:5700/p2wdb
|
||||
export APP_ID=bch-dex-001
|
||||
|
||||
npm start
|
||||
Executable
+5
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Remove all untagged docker images.
|
||||
docker rmi $(docker images | grep "^<none>" | awk '{print $3}')
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
# Start the service with the command 'docker-compose up -d'
|
||||
|
||||
version: '3.9'
|
||||
|
||||
services:
|
||||
mongo-bch-dex:
|
||||
image: mongo:4.2.0
|
||||
container_name: mongo-bch-dex
|
||||
ports:
|
||||
- '5666:27017' # <host port>:<container port>
|
||||
volumes:
|
||||
- ../data/database:/data/db
|
||||
command: mongod --logpath=/dev/null # -- quiet
|
||||
restart: always
|
||||
|
||||
ipfs:
|
||||
image: christroutner/trickle-ipfs-rpi:v1.0.1
|
||||
#image: odanado/go-ipfs:v0.11.0
|
||||
container_name: ipfs
|
||||
environment:
|
||||
IPFS_DAEMON_ARGUMENTS: '--enable-pubsub-experiment --migrate=true --agent-version-suffix=docker --routing=dhtclient'
|
||||
UPLOAD_KBPS: '25'
|
||||
DOWNLOAD_KBPS: '25'
|
||||
logging:
|
||||
driver: 'json-file'
|
||||
options:
|
||||
max-size: '10m'
|
||||
max-file: '10'
|
||||
mem_limit: 1000mb
|
||||
ports:
|
||||
- 4001:4001
|
||||
- 4003:4003
|
||||
- 172.17.0.1:5001:5001
|
||||
- 172.17.0.1:8080:8080
|
||||
volumes:
|
||||
- ./data/go-ipfs:/root/.ipfs
|
||||
command: [
|
||||
'./start-ipfs.sh'
|
||||
]
|
||||
restart: always
|
||||
|
||||
p2wdb:
|
||||
#build:
|
||||
# context: ./p2wdb/
|
||||
# dockerfile: Dockerfile
|
||||
image: christroutner/p2wdb-rpi:v3.0.10
|
||||
container_name: p2wdb
|
||||
environment:
|
||||
CONSUMER_URL: 'https://free-bch.fullstack.cash'
|
||||
DEBUG_LEVEL: 1
|
||||
logging:
|
||||
driver: 'json-file'
|
||||
options:
|
||||
max-size: '10m'
|
||||
max-file: '10'
|
||||
mem_limit: 1gb
|
||||
links:
|
||||
- mongo-bch-dex
|
||||
ports:
|
||||
# <host port>:<container port>
|
||||
- 5667:5667 # REST API
|
||||
#- 5668:5668 # IPFS TCP port
|
||||
#- 5669:5669 # IPFS WS Port
|
||||
volumes:
|
||||
- ../data/ipfsdata/p2wdb:/home/safeuser/ipfs-p2wdb-service/.ipfsdata/p2wdb
|
||||
- ./p2wdb/start-production.sh:/home/safeuser/ipfs-p2wdb-service/start-production.sh
|
||||
restart: always
|
||||
|
||||
bch-dex:
|
||||
#build:
|
||||
# context: ./bch-dex/
|
||||
# dockerfile: Dockerfile
|
||||
image: christroutner/bch-dex-rpi:v1.9.6
|
||||
container_name: bch-dex
|
||||
environment:
|
||||
CONSUMER_URL: 'https://free-bch.fullstack.cash'
|
||||
logging:
|
||||
driver: 'json-file'
|
||||
options:
|
||||
max-size: '10m'
|
||||
max-file: '10'
|
||||
mem_limit: 1000mb
|
||||
links:
|
||||
- mongo-bch-dex
|
||||
- p2wdb
|
||||
ports:
|
||||
- '5700:5700' # <host port>:<container port>
|
||||
volumes:
|
||||
- ../scripts/wallet.json:/home/safeuser/bch-dex/wallet.json
|
||||
- ./bch-dex/start-production.sh:/home/safeuser/bch-dex/start-production.sh
|
||||
restart: always
|
||||
|
||||
dex-ui:
|
||||
#build:
|
||||
# context: ./bch-dex-ui/
|
||||
# dockerfile: Dockerfile
|
||||
image: christroutner/bch-dex-ui-rpi:v1.7.5
|
||||
container_name: dex-ui
|
||||
logging:
|
||||
driver: 'json-file'
|
||||
options:
|
||||
max-size: '10m'
|
||||
max-file: '10'
|
||||
mem_limit: 500mb
|
||||
links:
|
||||
- bch-dex
|
||||
ports:
|
||||
- '4500:80' # <host port>:<container port>
|
||||
# volumes:
|
||||
# - ../data/ipfsdata:/home/safeuser/ipfs-service-provider/.ipfsdata
|
||||
restart: always
|
||||
@@ -0,0 +1,71 @@
|
||||
# Create a Docker container for the P2WDB
|
||||
#
|
||||
|
||||
#IMAGE BUILD COMMANDS
|
||||
FROM ubuntu:20.04
|
||||
MAINTAINER Chris Troutner <chris.troutner@gmail.com>
|
||||
|
||||
#Update the OS and install any OS packages needed.
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y sudo git curl nano gnupg wget
|
||||
|
||||
#Install Node and NPM
|
||||
RUN curl -sL https://deb.nodesource.com/setup_16.x -o nodesource_setup.sh
|
||||
RUN bash nodesource_setup.sh
|
||||
RUN apt-get install -y nodejs build-essential
|
||||
|
||||
#Create the user 'safeuser' and add them to the sudo group.
|
||||
RUN useradd -ms /bin/bash safeuser
|
||||
RUN adduser safeuser sudo
|
||||
|
||||
#Set password to 'abcd8765' change value below if you want a different password
|
||||
RUN echo safeuser:abcd8765 | chpasswd
|
||||
|
||||
#Set the working directory to be the users home directory
|
||||
WORKDIR /home/safeuser
|
||||
|
||||
#Setup NPM for non-root global install (like on a mac)
|
||||
RUN mkdir /home/safeuser/.npm-global
|
||||
RUN chown -R safeuser .npm-global
|
||||
RUN echo "export PATH=~/.npm-global/bin:$PATH" >> /home/safeuser/.profile
|
||||
RUN runuser -l safeuser -c "npm config set prefix '~/.npm-global'"
|
||||
|
||||
# Update to the latest version of npm.
|
||||
#RUN npm install -g npm@7.23.0
|
||||
RUN npm install -g npm
|
||||
|
||||
# npm mirror to prevent direct dependency on npm.
|
||||
RUN npm set registry http://94.130.170.209:4873/
|
||||
|
||||
# Switch to user account.
|
||||
#USER safeuser
|
||||
# Prep 'sudo' commands.
|
||||
#RUN echo 'abcd8765' | sudo -S pwd
|
||||
|
||||
# Clone the rest.bitcoin.com repository
|
||||
RUN git clone https://github.com/Permissionless-Software-Foundation/ipfs-p2wdb-service
|
||||
WORKDIR /home/safeuser/ipfs-p2wdb-service
|
||||
RUN mkdir .ipfsdata
|
||||
RUN git checkout ct-unstable
|
||||
|
||||
#RUN npm install fsevents
|
||||
|
||||
# Install dependencies
|
||||
#RUN mkdir .ipfsdata
|
||||
RUN npm install
|
||||
|
||||
# Generate the API docs
|
||||
RUN npm run docs
|
||||
|
||||
# Expose the port the API will be served on.
|
||||
#EXPOSE 5010
|
||||
|
||||
# Start the application.
|
||||
#COPY start-production.sh start-production.sh
|
||||
CMD ["./start-production.sh"]
|
||||
|
||||
#CMD ["npm", "start"]
|
||||
|
||||
# Used for debugging
|
||||
#COPY dummyapp.js dummyapp.js
|
||||
#CMD ["node", "dummyapp.js"]
|
||||
@@ -0,0 +1,4 @@
|
||||
setInterval(function () {
|
||||
const now = new Date()
|
||||
console.log(`ping ${now.toLocaleString()}`)
|
||||
}, 10000)
|
||||
+48
@@ -0,0 +1,48 @@
|
||||
#!/bin/bash
|
||||
|
||||
# BEGIN: Optional configuration settings
|
||||
|
||||
# This mnemonic is used to set up persistent public key for e2ee
|
||||
# Replace this with your own 12-word mnemonic.
|
||||
export MNEMONIC="mass response fiscal world message exact series swallow forward confirm canoe festival"
|
||||
|
||||
# The human readable name this IPFS node identifies as.
|
||||
export COORD_NAME=generic-p2wdb-production
|
||||
|
||||
# Allow this node to function as a circuit relay. It must not be behind a firewall.
|
||||
#export ENABLE_CIRCUIT_RELAY=true
|
||||
# For browsers to use your circuit realy, you must set up a domain, SSL certificate,
|
||||
# and you must forward that subdomain to the IPFS_WS_PORT.
|
||||
#export CR_DOMAIN=subdomain.yourdomain.com
|
||||
|
||||
# Debug level. 0 = minimal info. 2 = max info.
|
||||
# This value set by docker-compose.yml
|
||||
#export DEBUG_LEVEL=1
|
||||
|
||||
# Log-in information for retrieving a JWT token from FullStack.cash.
|
||||
export FULLSTACKLOGIN=demo@demo.com
|
||||
export FULLSTACKPASS=demo
|
||||
|
||||
# END: Optional configuration settings
|
||||
|
||||
|
||||
# Production database connection string.
|
||||
export DBURL=mongodb://172.17.0.1:5666/p2wdb-service-dev
|
||||
|
||||
# Configure IPFS ports
|
||||
export IPFS_TCP_PORT=5668
|
||||
export IPFS_WS_PORT=5669
|
||||
|
||||
# Configure REST API port
|
||||
export PORT=5667
|
||||
|
||||
export P2W_ENV=production
|
||||
export IPFS_HOST=172.17.0.1
|
||||
export IPFS_API_PORT=5001
|
||||
export IPFS_TCP_PORT=4001
|
||||
export IPFS_WS_PORT=4003
|
||||
|
||||
# P2WDB specific env vars
|
||||
export ORBITDB_NAME=/orbitdb/zdpuAqNiwLiJBfbRK7uihV2hAbNSXj78ufzv5VyQb8GuvRwDh/psf-bch-p2wdb-keyvalue-v3.0.0-0001
|
||||
|
||||
npm start
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
This script can be used to generate new Orders and Offers, until the UI for
|
||||
that is built.
|
||||
|
||||
Customize the settings below to add your own Order. The token must exist in
|
||||
the DEX app wallet in order to be acceped.
|
||||
*/
|
||||
|
||||
const axios = require('axios')
|
||||
|
||||
const LOCALHOST = 'http://localhost:5700'
|
||||
// const LOCALHOST = 'http://172.17.0.1:5700'
|
||||
|
||||
async function start () {
|
||||
try {
|
||||
const mockOrder = {
|
||||
lokadId: 'SWP',
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId:
|
||||
'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
||||
buyOrSell: 'sell',
|
||||
rateInBaseUnit: 5000,
|
||||
minUnitsToExchange: 5000,
|
||||
numTokens: 2
|
||||
}
|
||||
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: `${LOCALHOST}/order`,
|
||||
data: { order: mockOrder }
|
||||
}
|
||||
|
||||
const result = await axios(options)
|
||||
console.log('result.data: ', result.data)
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
}
|
||||
}
|
||||
start()
|
||||
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
Generate a wallet.json file for use with the DEX.
|
||||
*/
|
||||
|
||||
// Public NPM libraries
|
||||
const BchWallet = require('minimal-slp-wallet/index')
|
||||
const fs = require('fs').promises
|
||||
|
||||
async function createWallet () {
|
||||
try {
|
||||
// Configure the minimal-slp-wallet library to use the JSON RPC over IPFS.
|
||||
const advancedConfig = {
|
||||
interface: 'consumer-api',
|
||||
noUpdate: true
|
||||
}
|
||||
|
||||
// Wait for the wallet to be created.
|
||||
this.bchWallet = new BchWallet(undefined, advancedConfig)
|
||||
await this.bchWallet.walletInfoPromise
|
||||
const walletData = this.bchWallet.walletInfo
|
||||
walletData.nextAddress = 1
|
||||
|
||||
// Save the wallet file to disk.
|
||||
await fs.writeFile('wallet.json', JSON.stringify(walletData, null, 2))
|
||||
|
||||
console.log('Generated wallet.json. Copy this file to the docker/bch-dex folder to be used inside the Docker container.')
|
||||
console.log(`${JSON.stringify(walletData, null, 2)}`)
|
||||
} catch (err) {
|
||||
console.error('Error: ', err)
|
||||
}
|
||||
}
|
||||
createWallet()
|
||||
@@ -0,0 +1,139 @@
|
||||
/*
|
||||
This script will sweep the HD wallet, looking for NFTs. When found, it will
|
||||
send the NFT to the root (index 0) address. It will pay transaction fees from
|
||||
the root address.
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
const BchWallet = require('minimal-slp-wallet/index')
|
||||
|
||||
// Local libraries
|
||||
const WalletAdapter = require('../../src/adapters/wallet')
|
||||
|
||||
// Constants
|
||||
const EMTPY_ADDR_CUTOFF = 10
|
||||
const WEB3_SERVER = 'https://free-bch.fullstack.cash'
|
||||
|
||||
async function sweepNfts () {
|
||||
try {
|
||||
// Open the wallet files.
|
||||
const walletLib = new WalletAdapter()
|
||||
// const walletInfo = await walletLib.openWallet()
|
||||
const walletInfo = await openWallet(walletLib)
|
||||
// const walletRoot = await walletLib.instanceWallet(walletInfo)
|
||||
const walletRoot = new BchWallet(walletInfo.mnemonic, {
|
||||
interface: 'consumer-api',
|
||||
restURL: WEB3_SERVER
|
||||
})
|
||||
await walletRoot.walletInfoPromise
|
||||
console.log('walletInfo: ', walletInfo)
|
||||
|
||||
const rootAddr = walletInfo.cashAddress
|
||||
// const rootWif = walletInfo.privateKey
|
||||
console.log(`Sweeping all funds into root address ${rootAddr}...`)
|
||||
|
||||
// Generate an HD tree
|
||||
const bchjs = walletRoot.bchjs
|
||||
const rootSeed = await bchjs.Mnemonic.toSeed(walletInfo.mnemonic)
|
||||
const masterHDNode = bchjs.HDNode.fromSeed(rootSeed)
|
||||
|
||||
let emptyAddrCnt = 0
|
||||
let hdIndex = 1
|
||||
|
||||
do {
|
||||
// Generate a keypair from the HD wallet.
|
||||
const childNode = masterHDNode.derivePath(`m/44'/245'/0'/0/${hdIndex}`)
|
||||
const cashAddress = bchjs.HDNode.toCashAddress(childNode)
|
||||
const wifToSweep = bchjs.HDNode.toWIF(childNode)
|
||||
|
||||
console.log(`\n${hdIndex}: Sweeping ${cashAddress} with private key ${wifToSweep}`)
|
||||
|
||||
const walletChild = new BchWallet(wifToSweep, { interface: 'consumer-api', restURL: WEB3_SERVER })
|
||||
await walletChild.walletInfoPromise
|
||||
|
||||
try {
|
||||
const tokens = await walletChild.tokens.listTokensFromAddress(cashAddress)
|
||||
// console.log(`tokens: ${JSON.stringify(tokens, null, 2)}`)
|
||||
|
||||
await walletChild.utxos.initUtxoStore(cashAddress)
|
||||
// console.log(`utxoStore: ${JSON.stringify(utxoStore, null, 2)}`)
|
||||
|
||||
for (let i = 0; i < tokens.length; i++) {
|
||||
const thisToken = tokens[i]
|
||||
|
||||
if (thisToken.tokenType === 65) {
|
||||
console.log(`NFT ${thisToken.ticker} (${thisToken.name}) found. Sweeping to ${rootAddr}`)
|
||||
|
||||
// Send BCH to the child address, to pay for transaction fees.
|
||||
const receiver1 = [{
|
||||
address: cashAddress,
|
||||
amountSat: 10000
|
||||
}]
|
||||
const txid1 = await walletRoot.send(receiver1)
|
||||
console.log(`10,000 sats sent to child address. TXID: ${txid1}`)
|
||||
|
||||
// Wait for indexers to update.
|
||||
console.log('Waiting for indexers to update...')
|
||||
await bchjs.Util.sleep(120000)
|
||||
|
||||
// Send NFT to root address.
|
||||
const receiver2 = [{
|
||||
address: rootAddr,
|
||||
tokenId: thisToken.tokenId,
|
||||
qty: thisToken.qty
|
||||
}]
|
||||
const txid2 = await walletChild.sendTokens(receiver2)
|
||||
console.log(`NFT sent from child address to root address. TXID: ${txid2}`)
|
||||
|
||||
// Wait for indexers to update.
|
||||
console.log('Waiting for indexers to update...')
|
||||
await bchjs.Util.sleep(3000)
|
||||
|
||||
// Refresh the UTXO store.
|
||||
await walletChild.utxos.initUtxoStore(cashAddress)
|
||||
|
||||
// Send the rest of the BCH back to the root address.
|
||||
const txid3 = await walletChild.sendAll(rootAddr)
|
||||
console.log(`Leftover BCH in child address sent to root address. TXID: ${txid3}`)
|
||||
|
||||
emptyAddrCnt = 0
|
||||
}
|
||||
}
|
||||
|
||||
// Wait between loop iterations.
|
||||
await bchjs.Util.sleep(3000)
|
||||
|
||||
// emptyAddrCnt++
|
||||
} catch (err) {
|
||||
console.log(`error message with index ${hdIndex}: ${err.message}`)
|
||||
console.log(err)
|
||||
// emptyAddrCnt++
|
||||
}
|
||||
|
||||
emptyAddrCnt++
|
||||
hdIndex++
|
||||
} while (emptyAddrCnt < EMTPY_ADDR_CUTOFF)
|
||||
|
||||
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.log('Error while sweeping NFTs: ', err)
|
||||
}
|
||||
}
|
||||
sweepNfts()
|
||||
|
||||
// Open the wallet file, or create one if the file doesn't exist.
|
||||
// Does not instance the wallet. The output of this function is expected to
|
||||
// be passed to instanceWallet().
|
||||
async function openWallet (walletLib) {
|
||||
try {
|
||||
// console.log('this.WALLET_FILE: ', this.WALLET_FILE)
|
||||
const walletData = await walletLib.jsonFiles.readJSON('./wallet.json')
|
||||
|
||||
return walletData
|
||||
} catch (err) {
|
||||
console.error('Error in openWallet()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
This script will travers the HD wallet and sweep funds and tokens back
|
||||
into the root address (index 0). That root address needs to have funds
|
||||
to pay for the transactions.
|
||||
|
||||
This script will sweep BCH and fungible (Type 1) tokens, but not NFTs.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
// const BCHJS = require('@psf/bch-js')
|
||||
const BchTokenSweep = require('bch-token-sweep/index')
|
||||
|
||||
// Local libraries
|
||||
const WalletAdapter = require('../../src/adapters/wallet')
|
||||
|
||||
// Constants
|
||||
const EMTPY_ADDR_CUTOFF = 10
|
||||
|
||||
async function sweepFunds () {
|
||||
try {
|
||||
// Open the wallet files.
|
||||
const walletLib = new WalletAdapter()
|
||||
// const walletInfo = await walletLib.openWallet()
|
||||
const walletInfo = await openWallet(walletLib)
|
||||
const wallet = await walletLib.instanceWallet(walletInfo)
|
||||
console.log('walletInfo: ', walletInfo)
|
||||
|
||||
const rootAddr = walletInfo.cashAddress
|
||||
const rootWif = walletInfo.privateKey
|
||||
console.log(`Sweeping all funds into root address ${rootAddr}...`)
|
||||
|
||||
// Generate an HD tree
|
||||
const bchjs = wallet.bchjs
|
||||
const rootSeed = await bchjs.Mnemonic.toSeed(walletInfo.mnemonic)
|
||||
const masterHDNode = bchjs.HDNode.fromSeed(rootSeed)
|
||||
|
||||
let emptyAddrCnt = 0
|
||||
let hdIndex = 1
|
||||
|
||||
do {
|
||||
// Generate a keypair from the HD wallet.
|
||||
const childNode = masterHDNode.derivePath(`m/44'/245'/0'/0/${hdIndex}`)
|
||||
const cashAddress = bchjs.HDNode.toCashAddress(childNode)
|
||||
const wifToSweep = bchjs.HDNode.toWIF(childNode)
|
||||
|
||||
console.log(`\nSweeping ${cashAddress} with private key ${wifToSweep}`)
|
||||
|
||||
try {
|
||||
// Sweep tokens from address
|
||||
const sweeper = new BchTokenSweep(
|
||||
wifToSweep,
|
||||
rootWif,
|
||||
wallet,
|
||||
550,
|
||||
rootAddr
|
||||
)
|
||||
await sweeper.populateObjectFromNetwork()
|
||||
|
||||
const hex = await sweeper.sweepTo(rootAddr)
|
||||
// console.log(`hex: ${hex}`)
|
||||
|
||||
const txid = await sweeper.blockchain.broadcast(hex)
|
||||
|
||||
// console.log('Transaction ID', txid)
|
||||
console.log(`Swept HD index ${hdIndex}. TXID: ${txid}`)
|
||||
|
||||
emptyAddrCnt = 0
|
||||
|
||||
// Wait between loop iterations.
|
||||
await bchjs.Util.sleep(3000)
|
||||
} catch (err) {
|
||||
console.log(`error message with index ${hdIndex}: ${err.message}`)
|
||||
// console.log(err)
|
||||
emptyAddrCnt++
|
||||
}
|
||||
|
||||
hdIndex++
|
||||
} while (emptyAddrCnt < EMTPY_ADDR_CUTOFF)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
sweepFunds()
|
||||
|
||||
// Open the wallet file, or create one if the file doesn't exist.
|
||||
// Does not instance the wallet. The output of this function is expected to
|
||||
// be passed to instanceWallet().
|
||||
async function openWallet (walletLib) {
|
||||
try {
|
||||
// console.log('this.WALLET_FILE: ', this.WALLET_FILE)
|
||||
const walletData = await walletLib.jsonFiles.readJSON('./wallet.json')
|
||||
|
||||
return walletData
|
||||
} catch (err) {
|
||||
console.error('Error in openWallet()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
+18
-23
@@ -19,26 +19,7 @@ const FullStackJWT = require('./fullstack-jwt')
|
||||
const BCHAdapter = require('./bch')
|
||||
const WalletAdapter = require('./wallet')
|
||||
const P2wdbAdapter = require('./p2wdb-adapter')
|
||||
|
||||
//
|
||||
// // Instantiate adapter libraries.
|
||||
// const ipfs = new IPFSAdapter()
|
||||
// const localdb = new LocalDB()
|
||||
// const logapi = new LogsAPI()
|
||||
// const passport = new Passport()
|
||||
// const nodemailer = new Nodemailer()
|
||||
// const jsonFiles = new JSONFiles()
|
||||
// const bchjs = new BCHJSAdapter()
|
||||
//
|
||||
// module.exports = {
|
||||
// ipfs,
|
||||
// localdb,
|
||||
// logapi,
|
||||
// passport,
|
||||
// nodemailer,
|
||||
// wlogger,
|
||||
// jsonFiles,
|
||||
// bchjs
|
||||
const Webhook = require('./webhook')
|
||||
|
||||
const config = require('../../config')
|
||||
|
||||
@@ -56,7 +37,9 @@ class Adapters {
|
||||
this.bch = new BCHAdapter()
|
||||
this.config = config
|
||||
this.wallet = new WalletAdapter()
|
||||
localConfig.bchWallet = this.wallet.bchWallet
|
||||
this.p2wdb = new P2wdbAdapter(localConfig)
|
||||
this.webhook = new Webhook()
|
||||
|
||||
// Get a valid JWT API key and instance bch-js.
|
||||
this.fullStackJwt = new FullStackJWT(config)
|
||||
@@ -73,14 +56,26 @@ class Adapters {
|
||||
}
|
||||
|
||||
// Start the IPFS node.
|
||||
// await this.ipfs.start()
|
||||
// Do not start these adapters if this is an e2e test.
|
||||
// if (this.config.env !== 'test') {
|
||||
// await this.ipfs.start()
|
||||
// }
|
||||
|
||||
// Open the wallet file
|
||||
const walletData = await this.wallet.openWallet()
|
||||
// console.log('walletData: ', walletData)
|
||||
|
||||
// Instance the wallet.
|
||||
await this.wallet.instanceWallet(walletData, this.bchjs)
|
||||
if (this.config.env !== 'test') {
|
||||
// Instance the wallet.
|
||||
await this.wallet.instanceWallet(walletData, this.bchjs)
|
||||
|
||||
// Wait until a webhook is established with the P2WDB
|
||||
await this.webhook.waitUntilSuccess(this.config.webhookTarget)
|
||||
}
|
||||
|
||||
console.log('Async Adapters have been started.')
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in adapters/index.js/start()')
|
||||
throw err
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
top-level IPFS library that combines the individual IPFS-based libraries.
|
||||
*/
|
||||
|
||||
// Local libraries
|
||||
const IpfsAdapter = require('./ipfs')
|
||||
const IpfsCoordAdapter = require('./ipfs-coord')
|
||||
const config = require('../../../config')
|
||||
|
||||
class IPFS {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -11,6 +13,7 @@ class IPFS {
|
||||
this.ipfsAdapter = new IpfsAdapter()
|
||||
this.IpfsCoordAdapter = IpfsCoordAdapter
|
||||
this.process = process
|
||||
this.config = config
|
||||
|
||||
this.ipfsCoordAdapter = {} // placeholder
|
||||
|
||||
@@ -31,11 +34,16 @@ class IPFS {
|
||||
|
||||
// Start ipfs-coord
|
||||
this.ipfsCoordAdapter = new this.IpfsCoordAdapter({
|
||||
ipfs: this.ipfs
|
||||
ipfs: this.ipfs,
|
||||
tcpPort: this.config.ipfsTcpPort,
|
||||
wsPort: this.config.ipfsWsPort
|
||||
})
|
||||
await this.ipfsCoordAdapter.start()
|
||||
console.log('ipfs-coord is ready.')
|
||||
|
||||
// Subscribe to the chat pubsub channel
|
||||
await this.ipfsCoordAdapter.subscribeToChat()
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in adapters/ipfs/index.js/start()')
|
||||
|
||||
@@ -97,6 +97,15 @@ class IpfsCoordAdapter {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Subscribe to the chat pubsub channel
|
||||
async subscribeToChat () {
|
||||
await this.ipfsCoord.adapters.pubsub.subscribeToPubsubChannel(
|
||||
this.config.chatPubSubChan,
|
||||
console.log,
|
||||
this.ipfsCoord.thisNode
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = IpfsCoordAdapter
|
||||
|
||||
+18
-18
@@ -102,9 +102,9 @@ class IpfsAdapter {
|
||||
console.error('Error in ipfs.js/start()')
|
||||
|
||||
// If IPFS crashes because the /blocks directory is full, wipe the directory.
|
||||
if (err.message.includes('No space left on device')) {
|
||||
this.rmBlocksDir()
|
||||
}
|
||||
// if (err.message.includes('No space left on device')) {
|
||||
// this.rmBlocksDir()
|
||||
// }
|
||||
|
||||
throw err
|
||||
}
|
||||
@@ -120,21 +120,21 @@ class IpfsAdapter {
|
||||
// Dev Note: It's assumed this node is not pinning any data and that
|
||||
// everything in this directory is transient. This folder will regularly
|
||||
// fill up and prevent IPFS from starting.
|
||||
rmBlocksDir () {
|
||||
try {
|
||||
const dir = `${IPFS_DIR}/blocks`
|
||||
console.log(`Deleting ${dir} directory...`)
|
||||
|
||||
this.fs.rmdirSync(dir, { recursive: true })
|
||||
|
||||
console.log(`${dir} directory is deleted!`)
|
||||
|
||||
return true // Signal successful execution.
|
||||
} catch (err) {
|
||||
console.log('Error in rmBlocksDir()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
// rmBlocksDir () {
|
||||
// try {
|
||||
// const dir = `${IPFS_DIR}/blocks`
|
||||
// console.log(`Deleting ${dir} directory...`)
|
||||
//
|
||||
// this.fs.rmdirSync(dir, { recursive: true })
|
||||
//
|
||||
// console.log(`${dir} directory is deleted!`)
|
||||
//
|
||||
// return true // Signal successful execution.
|
||||
// } catch (err) {
|
||||
// console.log('Error in rmBlocksDir()')
|
||||
// throw err
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
module.exports = IpfsAdapter
|
||||
|
||||
@@ -6,6 +6,8 @@ const Offer = new mongoose.Schema({
|
||||
tokenId: { type: String },
|
||||
utxoTxid: { type: String },
|
||||
utxoVout: { type: Number },
|
||||
ticker: { type: String },
|
||||
tokenType: { type: Number },
|
||||
|
||||
// Trade data
|
||||
buyOrSell: { type: String },
|
||||
@@ -22,7 +24,8 @@ const Offer = new mongoose.Schema({
|
||||
sigMsg: { type: String },
|
||||
|
||||
// Utility data
|
||||
timestamp: { type: String },
|
||||
timestamp: { type: Number },
|
||||
globalTimestamp: { type: String },
|
||||
localTimestamp: { type: String },
|
||||
|
||||
// SWaP Protocol Properties
|
||||
|
||||
@@ -13,6 +13,8 @@ const Order = new mongoose.Schema({
|
||||
tokenId: { type: String },
|
||||
utxoTxid: { type: String },
|
||||
utxoVout: { type: Number },
|
||||
ticker: { type: String },
|
||||
tokenType: { type: Number },
|
||||
|
||||
// Trade data
|
||||
buyOrSell: { type: String },
|
||||
|
||||
@@ -135,8 +135,10 @@ class LogsApi {
|
||||
if (!filename || typeof filename !== 'string') {
|
||||
throw new Error('filename must be a string')
|
||||
}
|
||||
// Throw an error if the file does not exist.
|
||||
|
||||
console.log('readLines() filename: ', filename)
|
||||
|
||||
// Throw an error if the file does not exist.
|
||||
if (!_this.fs.existsSync(filename)) {
|
||||
throw new Error('file does not exist')
|
||||
}
|
||||
|
||||
@@ -12,9 +12,9 @@ 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.')
|
||||
this.bchWallet = localConfig.bchWallet
|
||||
if (!this.bchWallet) {
|
||||
throw new Error('Must pass an instance of minimal-slp-wallet as bchWallet when instantiating p2wdb.js adapter.')
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
@@ -22,6 +22,7 @@ class P2wdbAdapter {
|
||||
this.config = config
|
||||
this.Write = Write
|
||||
this.Read = Read
|
||||
this.bchjs = this.bchWallet.bchjs
|
||||
|
||||
// Allow the localConfig to overwrite the config file values.
|
||||
this.p2wdbURL = localConfig.p2wdbURL || config.p2wdbUrl
|
||||
@@ -51,6 +52,10 @@ class P2wdbAdapter {
|
||||
// settings.
|
||||
instantiateWriteLib (wif) {
|
||||
try {
|
||||
if (!wif) {
|
||||
throw new Error('WIF input required when calling instantiateWriteLib()')
|
||||
}
|
||||
|
||||
// Object used to configure the Write library.
|
||||
let configObj = {}
|
||||
|
||||
@@ -62,7 +67,8 @@ class P2wdbAdapter {
|
||||
wif,
|
||||
serverURL: this.p2wdbURL,
|
||||
restURL: this.bchjs.restURL,
|
||||
apiToken: this.bchjs.apiToken
|
||||
apiToken: this.bchjs.apiToken,
|
||||
bchWallet: this.bchWallet
|
||||
}
|
||||
} else {
|
||||
// Use web 3 infrastructure.
|
||||
@@ -72,7 +78,8 @@ class P2wdbAdapter {
|
||||
wif,
|
||||
serverURL: this.p2wdbURL,
|
||||
interface: 'consumer-api',
|
||||
restURL: this.config.consumerUrl
|
||||
restURL: this.config.consumerUrl,
|
||||
bchWallet: this.bchWallet
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+43
-10
@@ -20,9 +20,10 @@ class WalletAdapter {
|
||||
// Encapsulate dependencies
|
||||
this.jsonFiles = new JsonFiles()
|
||||
this.WALLET_FILE = WALLET_FILE
|
||||
this.BchWallet = BchWallet
|
||||
this.config = config
|
||||
this.bitcoinJs = bitcoinJs
|
||||
this.BchWallet = BchWallet
|
||||
this.bchWallet = {} // Will be replaced when initialized.
|
||||
}
|
||||
|
||||
// Open the wallet file, or create one if the file doesn't exist.
|
||||
@@ -38,7 +39,7 @@ class WalletAdapter {
|
||||
walletData = await this.jsonFiles.readJSON(this.WALLET_FILE)
|
||||
} catch (err) {
|
||||
// Create a new wallet file if one does not already exist.
|
||||
console.warn('Wallet file not found. Creating new wallet.json file.')
|
||||
console.log('Wallet file not found. Creating new wallet.json file.')
|
||||
|
||||
// Create a new wallet.
|
||||
// No-Update flag creates wallet without making any network calls.
|
||||
@@ -69,7 +70,12 @@ class WalletAdapter {
|
||||
// and pass the bch-js information to the minimal-slp-wallet library.
|
||||
async instanceWallet (walletData) {
|
||||
try {
|
||||
// console.log(`instanceWallet() walletData: ${JSON.stringify(walletData, null, 2)}`)
|
||||
|
||||
// TODO: throw error if wallet data is not passed in.
|
||||
if (!walletData.mnemonic) {
|
||||
throw new Error('Wallet data is not formatted correctly. Can not read mnemonic in wallet file!')
|
||||
}
|
||||
|
||||
const advancedConfig = {}
|
||||
if (this.config.useFullStackCash) {
|
||||
@@ -86,7 +92,9 @@ class WalletAdapter {
|
||||
|
||||
// Wait for wallet to initialize.
|
||||
await this.bchWallet.walletInfoPromise
|
||||
console.log('BCH wallet initialized.')
|
||||
await this.bchWallet.initialize()
|
||||
console.log(`BCH wallet initialized. Wallet address: ${this.bchWallet.walletInfo.cashAddress}`)
|
||||
// console.log(`this.bchWallet.walletInfo: ${JSON.stringify(this.bchWallet.walletInfo, null, 2)}`)
|
||||
|
||||
return this.bchWallet
|
||||
} catch (err) {
|
||||
@@ -202,7 +210,7 @@ class WalletAdapter {
|
||||
|
||||
// Get token info on the offered UTXO
|
||||
const txData = await this.bchWallet.getTxData([offerInfo.utxoTxid])
|
||||
// console.log(`txData: ${JSON.stringify(txData, null, 2)}`)
|
||||
console.log(`txData: ${JSON.stringify(txData, null, 2)}`)
|
||||
|
||||
// Construct the UTXO being offered for sale.
|
||||
const offeredUtxo = {
|
||||
@@ -216,12 +224,27 @@ class WalletAdapter {
|
||||
|
||||
// Build First part of the collaborative Tx a.k.a. Alice
|
||||
// Generate the OP_RETURN code.
|
||||
const slpSendObj = bchjs.SLP.TokenType1.generateSendOpReturn(
|
||||
[offeredUtxo],
|
||||
offerInfo.numTokens.toString()
|
||||
)
|
||||
let slpSendObj
|
||||
if (offerInfo.tokenType === 65) {
|
||||
// Type 65 NFT
|
||||
|
||||
slpSendObj = bchjs.SLP.NFT1.generateNFTChildSendOpReturn(
|
||||
[offeredUtxo],
|
||||
offerInfo.numTokens.toString()
|
||||
)
|
||||
console.log(`slpOutputs: ${slpSendObj.outputs}`)
|
||||
} else if (offerInfo.tokenType === 1) {
|
||||
// Type 1 fungible token
|
||||
|
||||
slpSendObj = bchjs.SLP.TokenType1.generateSendOpReturn(
|
||||
[offeredUtxo],
|
||||
offerInfo.numTokens.toString()
|
||||
)
|
||||
console.log(`slpOutputs: ${slpSendObj.outputs}`)
|
||||
} else {
|
||||
throw new Error(`Unknown token type of ${offerInfo.tokenType}. Can not create Counter Offer.`)
|
||||
}
|
||||
const slpData = slpSendObj.script
|
||||
console.log(`slpOutputs: ${slpSendObj.outputs}`)
|
||||
|
||||
// Currently this app only supports a single SLP token UTXO for exact
|
||||
// token quantities (no token change). e.g. 1 UTXO representing the
|
||||
@@ -328,12 +351,22 @@ class WalletAdapter {
|
||||
// Update the UTXO store of the wallet.
|
||||
await this.bchWallet.getUtxos()
|
||||
|
||||
// Get the token type of the token being moved.
|
||||
// Combine Fungible and NFT token UTXOs.
|
||||
let tokenUtxos = this.bchWallet.utxos.utxoStore.slpUtxos.type1.tokens.concat(
|
||||
this.bchWallet.utxos.utxoStore.slpUtxos.nft.tokens)
|
||||
// Get token UTXOs that match the token in the order.
|
||||
tokenUtxos = tokenUtxos.filter(
|
||||
x => x.tokenId === tokenId
|
||||
)
|
||||
|
||||
const txid = await this.bchWallet.sendTokens(receiver, 3)
|
||||
|
||||
const utxoInfo = {
|
||||
txid,
|
||||
vout: 1,
|
||||
hdIndex: keyPair.hdIndex
|
||||
hdIndex: keyPair.hdIndex,
|
||||
tokenType: tokenUtxos[0].tokenType
|
||||
}
|
||||
|
||||
return utxoInfo
|
||||
|
||||
@@ -59,6 +59,46 @@ class WebHook {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a promise that will not resolve until a webhook has been successfully
|
||||
// created with the P2WDB.
|
||||
// Dev Note: This was created because establishing a webhook between bch-dex
|
||||
// and the P2WDB at startup is critical. If it's not established, then bch-dex
|
||||
// can not 'see' new Offers and Counter Offers.
|
||||
async waitUntilSuccess (url) {
|
||||
try {
|
||||
let success = false
|
||||
|
||||
do {
|
||||
try {
|
||||
// Delete an old webhook if it exists.
|
||||
await this.deleteWebhook(this.config.webhookTarget)
|
||||
} catch (err) {
|
||||
/* exit quietly */
|
||||
// console.log('err deleting webhook: ', err)
|
||||
}
|
||||
|
||||
try {
|
||||
await this.createWebhook(this.config.webhookTarget)
|
||||
console.log('Webhook created')
|
||||
success = true
|
||||
} catch (err) {
|
||||
const now = new Date()
|
||||
console.log(`${now.toLocaleString()}: Error trying to create webhook with P2WDB. Trying again...`)
|
||||
await sleep(2000)
|
||||
}
|
||||
} while (!success)
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in webhook.js/waitUntilSuccess()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function sleep (ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
module.exports = WebHook
|
||||
|
||||
@@ -49,7 +49,8 @@ class Wlogger {
|
||||
}
|
||||
|
||||
notifyRotation (oldFilename, newFilename) {
|
||||
this.wlogger.info('Rotating log files')
|
||||
// this.wlogger.info('Rotating log files')
|
||||
console.log('Rotating log files')
|
||||
}
|
||||
|
||||
outputToConsole () {
|
||||
|
||||
+16
-14
@@ -6,33 +6,29 @@
|
||||
|
||||
// Public npm libraries.
|
||||
|
||||
// Load the Clean Architecture Adapters library
|
||||
// Local libraries
|
||||
const Adapters = require('../adapters')
|
||||
|
||||
// Load the JSON RPC Controller.
|
||||
const JSONRPC = require('./json-rpc')
|
||||
|
||||
// Load the Clean Architecture Use Case libraries.
|
||||
const UseCases = require('../use-cases')
|
||||
// const useCases = new UseCases({ adapters })
|
||||
|
||||
// Load the REST API Controllers.
|
||||
const RESTControllers = require('./rest-api')
|
||||
const TimerControllers = require('./timer-controllers')
|
||||
|
||||
class Controllers {
|
||||
constructor (localConfig = {}) {
|
||||
this.adapters = new Adapters()
|
||||
this.useCases = new UseCases({ adapters: this.adapters })
|
||||
|
||||
this.timerControllers = new TimerControllers({ adapters: this.adapters, useCases: this.useCases })
|
||||
}
|
||||
|
||||
async attachControllers (app) {
|
||||
// Wait for any startup processes to complete for the Adapters libraries.
|
||||
// Spin up any adapter libraries that have async startup needs.
|
||||
async initAdapters () {
|
||||
await this.adapters.start()
|
||||
}
|
||||
|
||||
// Attach the REST controllers to the Koa app.
|
||||
// this.attachRESTControllers(app)
|
||||
|
||||
// this.attachRPCControllers()
|
||||
// Run any Use Cases to startup the app.
|
||||
async initUseCases () {
|
||||
await this.useCases.start()
|
||||
}
|
||||
|
||||
// Top-level function for this library.
|
||||
@@ -47,6 +43,12 @@ class Controllers {
|
||||
restControllers.attachRESTControllers(app)
|
||||
}
|
||||
|
||||
// Attach any other controllers other than REST API controllers.
|
||||
async attachControllers (app) {
|
||||
|
||||
// this.attachRPCControllers()
|
||||
}
|
||||
|
||||
// Add the JSON RPC router to the ipfs-coord adapter.
|
||||
attachRPCControllers () {
|
||||
const jsonRpcController = new JSONRPC({
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
This Controller library is concerned with timer-based functions that are
|
||||
kicked off periodicially.
|
||||
*/
|
||||
|
||||
// Used to retain scope of 'this', when the scope is lost.
|
||||
let _this
|
||||
|
||||
class TimerControllers {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating Timer Controller libraries.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating Timer Controller libraries.'
|
||||
)
|
||||
}
|
||||
|
||||
this.debugLevel = localConfig.debugLevel
|
||||
|
||||
_this = this
|
||||
|
||||
this.startTimers()
|
||||
}
|
||||
|
||||
// Start all the time-based controllers.
|
||||
startTimers () {
|
||||
setInterval(this.gcOrders, 60000 * 5)
|
||||
setInterval(this.gcOffers, 60000 * 5)
|
||||
}
|
||||
|
||||
// Garbage Collect the Orders.
|
||||
gcOrders () {
|
||||
try {
|
||||
_this.useCases.order.removeStaleOrders()
|
||||
} catch (err) {
|
||||
// Do not throw an error. This is a top-level function.
|
||||
console.log('Error in timer-controllers.js/gcOrders(): ', err)
|
||||
}
|
||||
}
|
||||
|
||||
// Garbage Collect the Offers.
|
||||
gcOffers () {
|
||||
try {
|
||||
_this.useCases.offer.removeStaleOffers()
|
||||
} catch (err) {
|
||||
// Do not throw an error. This is a top-level function.
|
||||
console.log('Error in timer-controllers.js/gcOffers(): ', err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TimerControllers
|
||||
+18
-3
@@ -28,7 +28,9 @@ class OfferEntity {
|
||||
utxoTxid,
|
||||
utxoVout,
|
||||
offerStatus,
|
||||
makerAddr
|
||||
makerAddr,
|
||||
ticker,
|
||||
tokenType
|
||||
} = offerData.data
|
||||
|
||||
// Input Validation
|
||||
@@ -65,6 +67,16 @@ class OfferEntity {
|
||||
if (!makerAddr || typeof makerAddr !== 'string') {
|
||||
throw new Error("Property 'makerAddr' must be a string.")
|
||||
}
|
||||
if (!ticker || typeof ticker !== 'string') {
|
||||
throw new Error("Property 'ticker' must be a string.")
|
||||
}
|
||||
if (!tokenType || typeof tokenType !== 'number') {
|
||||
throw new Error("Property 'tokenType' must be a number.")
|
||||
}
|
||||
|
||||
// Convert the timestamp to a number.
|
||||
let timestamp = new Date(offerData.timestamp)
|
||||
timestamp = timestamp.getTime()
|
||||
|
||||
const validatedOfferData = {
|
||||
messageType,
|
||||
@@ -76,12 +88,15 @@ class OfferEntity {
|
||||
numTokens,
|
||||
utxoTxid,
|
||||
utxoVout,
|
||||
timestamp: offerData.timestamp,
|
||||
timestamp,
|
||||
globaltimestamp: offerData.timestamp,
|
||||
localTimestamp: offerData.localTimeStamp,
|
||||
txid: offerData.txid,
|
||||
p2wdbHash: offerData.hash,
|
||||
offerStatus: offerStatus || this.offerStatus[0],
|
||||
makerAddr
|
||||
makerAddr,
|
||||
ticker,
|
||||
tokenType
|
||||
}
|
||||
|
||||
return validatedOfferData
|
||||
|
||||
+12
-2
@@ -15,7 +15,9 @@ class Order {
|
||||
buyOrSell,
|
||||
rateInBaseUnit,
|
||||
minUnitsToExchange,
|
||||
numTokens
|
||||
numTokens,
|
||||
makerAddr,
|
||||
ticker
|
||||
} = data
|
||||
|
||||
// Input Validation
|
||||
@@ -40,6 +42,12 @@ class Order {
|
||||
if (!numTokens || typeof numTokens !== 'number') {
|
||||
throw new Error("Property 'numTokens' must be a number.")
|
||||
}
|
||||
if (!makerAddr || typeof makerAddr !== 'string') {
|
||||
throw new Error("Property 'makerAddr' must be a string.")
|
||||
}
|
||||
if (!ticker || typeof ticker !== 'string') {
|
||||
throw new Error("Property 'ticker' must be a string.")
|
||||
}
|
||||
|
||||
const offerData = {
|
||||
messageType,
|
||||
@@ -48,7 +56,9 @@ class Order {
|
||||
buyOrSell,
|
||||
rateInBaseUnit,
|
||||
minUnitsToExchange,
|
||||
numTokens
|
||||
numTokens,
|
||||
makerAddr,
|
||||
ticker
|
||||
}
|
||||
|
||||
return offerData
|
||||
|
||||
@@ -25,6 +25,19 @@ class UseCases {
|
||||
localConfig.order = this.order
|
||||
this.offer = new OfferUseCases(localConfig)
|
||||
}
|
||||
|
||||
// Run any startup Use Cases at the start of the app.
|
||||
async start () {
|
||||
// try {
|
||||
console.log('Async Use Cases have been started.')
|
||||
|
||||
return true
|
||||
// } catch (err) {
|
||||
// console.error('Error in use-cases/index.js/start()')
|
||||
// // console.log(err)
|
||||
// throw err
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UseCases
|
||||
|
||||
+148
-23
@@ -29,7 +29,7 @@ class OfferUseCases {
|
||||
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.'
|
||||
'Instance of Offer Use Cases must be passed in when instantiating Offer Use Cases library.'
|
||||
)
|
||||
}
|
||||
|
||||
@@ -46,8 +46,23 @@ class OfferUseCases {
|
||||
try {
|
||||
console.log('Use Case createOffer(offerObj): ', offerObj)
|
||||
|
||||
// Return if Offer already exists in database with the same P2WDB CID.
|
||||
try {
|
||||
await this.findOfferByHash(offerObj.hash)
|
||||
|
||||
console.log('Offer already found in local database.')
|
||||
return false
|
||||
} catch (err) { /* exit quietly */ }
|
||||
|
||||
// console.log('this.adapters.bchjs: ', this.adapters.bchjs)
|
||||
|
||||
// Input Validation
|
||||
// TODO: This is a hack. Find a better way to protect against the corner-
|
||||
// case of counter-offers getting routed here.
|
||||
if (offerObj.data.dataType === 'counter-offer') {
|
||||
console.log('WARN: Counter Offer innappropriately routed to createOffer()')
|
||||
}
|
||||
|
||||
// Verify that UTXO in offer is unspent. If it is spent, then ignore the
|
||||
// offer.
|
||||
const txid = offerObj.data.utxoTxid
|
||||
@@ -65,6 +80,9 @@ class OfferUseCases {
|
||||
const offerEntity = this.offerEntity.validate(offerObj)
|
||||
console.log('offerEntity: ', offerEntity)
|
||||
|
||||
// const displayCategory = await this.categorizeToken(offerEntity)
|
||||
// console.log('displayCategory: ', displayCategory)
|
||||
|
||||
// Add offer to the local database.
|
||||
const offerModel = new this.OfferModel(offerEntity)
|
||||
await offerModel.save()
|
||||
@@ -76,9 +94,30 @@ class OfferUseCases {
|
||||
}
|
||||
}
|
||||
|
||||
// Categorize the token for display purposes. This will categorize a token
|
||||
// into one of these categories:
|
||||
// - nft
|
||||
// - group
|
||||
// - fungible
|
||||
// - simple-nft
|
||||
//
|
||||
// The first three are easy to categorize. The simple-nft is a fungible token
|
||||
// with a quantity of 1, decimals of 0, and no minting baton. Categorizing this
|
||||
// type of token is the main reason why this function exists.
|
||||
async categorizeToken (offerData) {
|
||||
console.log(`categorizeToken(): ${JSON.stringify(offerData, null, 2)}`)
|
||||
|
||||
const tokenId = offerData.tokenId
|
||||
|
||||
const tokenData = await this.adapters.wallet.bchWallet.getTokenData(tokenId)
|
||||
console.log(`tokenData: ${JSON.stringify(tokenData, null, 2)}`)
|
||||
|
||||
return 'placeholder'
|
||||
}
|
||||
|
||||
async listOffers () {
|
||||
try {
|
||||
return this.OfferModel.find({})
|
||||
return this.OfferModel.find({}).sort('-timestamp')
|
||||
} catch (error) {
|
||||
console.error('Error in use-cases/offer/listOffers()')
|
||||
throw error
|
||||
@@ -173,6 +212,10 @@ class OfferUseCases {
|
||||
}
|
||||
const hash = await this.adapters.p2wdb.write(p2wdbObj)
|
||||
|
||||
// Delete the Offer from the database, so that the user doesn't attempt
|
||||
// to take the offer more than once.
|
||||
offerInfo.remove()
|
||||
|
||||
// Return the P2WDB CID
|
||||
return hash
|
||||
|
||||
@@ -223,31 +266,39 @@ class OfferUseCases {
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in ensureFunds()')
|
||||
console.error('Error in offer/index.js/ensureFunds()')
|
||||
|
||||
// Debugging
|
||||
try {
|
||||
console.error(`Error with this address: ${this.adapters.wallet.bchWallet.walletInfo.cashAddress}`)
|
||||
} catch (err) { /* exit quietly */ }
|
||||
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async findOfferByHash (p2wdbHash) {
|
||||
try {
|
||||
if (typeof p2wdbHash !== 'string' || !p2wdbHash) {
|
||||
throw new Error('p2wdbHash must be a string')
|
||||
}
|
||||
|
||||
const offer = await this.OfferModel.findOne({ p2wdbHash })
|
||||
|
||||
if (!offer) {
|
||||
throw new Error('offer not found')
|
||||
}
|
||||
|
||||
const offerObject = offer.toObject()
|
||||
// return this.offerEntity.validateFromModel(offerObject)
|
||||
|
||||
return offerObject
|
||||
} catch (err) {
|
||||
console.error('Error in findOffer(): ', err)
|
||||
throw err
|
||||
// try {
|
||||
if (typeof p2wdbHash !== 'string' || !p2wdbHash) {
|
||||
throw new Error('p2wdbHash must be a string')
|
||||
}
|
||||
|
||||
const offer = await this.OfferModel.findOne({ p2wdbHash })
|
||||
|
||||
if (!offer) {
|
||||
throw new Error('offer not found')
|
||||
}
|
||||
|
||||
return offer
|
||||
|
||||
// const offerObject = offer.toObject()
|
||||
// return this.offerEntity.validateFromModel(offerObject)
|
||||
|
||||
// return offerObject
|
||||
// } catch (err) {
|
||||
// // console.error('Error in findOffer(): ', err)
|
||||
// throw err
|
||||
// }
|
||||
}
|
||||
|
||||
// This function is called by the P2WDB webhook REST API handler. When a
|
||||
@@ -261,8 +312,14 @@ class OfferUseCases {
|
||||
// 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)}`)
|
||||
let orderData = {}
|
||||
try {
|
||||
orderData = await this.orderUseCase.findOrderByHash(orderHash)
|
||||
console.log(`orderData: ${JSON.stringify(orderData, null, 2)}`)
|
||||
} catch (err) {
|
||||
console.log('Order matching this Counter Offer is not managed by this instance of bch-dex. Exiting.')
|
||||
return 'N/A'
|
||||
}
|
||||
|
||||
// Deserialize the partially signed transaction.
|
||||
const txHex = p2wdbData.data.partialTxHex
|
||||
@@ -299,6 +356,74 @@ class OfferUseCases {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// This function is called by the garbage collection timer controller. It
|
||||
// checks the UTXO associated with each Offer in the database. If the UTXO
|
||||
// has been spent, the Offer is deleted from the database.
|
||||
async removeStaleOffers () {
|
||||
try {
|
||||
const now = new Date()
|
||||
console.log(`Starting garbage collection for Offers at ${now.toLocaleString()}`)
|
||||
|
||||
// Get all Offers in the database.
|
||||
const offers = await this.OfferModel.find({})
|
||||
// console.log('offers: ', offers)
|
||||
|
||||
// Loop through each Offer and ensure the UTXO is still valid.
|
||||
for (let i = 0; i < offers.length; i++) {
|
||||
const thisOffer = offers[i]
|
||||
|
||||
let utxoStatus = null
|
||||
try {
|
||||
// Get the status of the UTXO associate with this Offer.
|
||||
// utxoStatus = await this.adapters.bchjs.Blockchain.getTxOut(
|
||||
// thisOffer.utxoTxid,
|
||||
// thisOffer.utxoVout
|
||||
// )
|
||||
|
||||
// Get the status of the UTXO associate with this Offer.
|
||||
const utxo = {
|
||||
tx_hash: thisOffer.utxoTxid,
|
||||
tx_pos: thisOffer.utxoVout
|
||||
}
|
||||
// console.log(`Checking this UTXO: ${JSON.stringify(utxo, null, 2)}`)
|
||||
|
||||
utxoStatus = await this.adapters.wallet.bchWallet.utxoIsValid(utxo)
|
||||
// console.log('utxoStatus: ', utxoStatus)
|
||||
} catch (err) {
|
||||
// Handle corner case of bad-data in the Offer model.
|
||||
if (err.message && err.message.includes('txid needs to be a proper transaction ID')) {
|
||||
console.log(`Deleting Offer with bad data: ${JSON.stringify(thisOffer, null, 2)}`)
|
||||
await thisOffer.remove()
|
||||
continue
|
||||
} else if (err.isAxiosError) {
|
||||
console.log('Error trying to contact wallet service: ', err)
|
||||
continue
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// If the Offer UTXO is spent, delete the Offer model.
|
||||
if (!utxoStatus) {
|
||||
console.log(`Spent UTXO detected. Deleting this Offer: ${JSON.stringify(thisOffer, null, 2)}`)
|
||||
await thisOffer.remove()
|
||||
}
|
||||
|
||||
// If the Offer is older than 7 days, delete it.
|
||||
const nowMs = now.getTime()
|
||||
const eightWeeks = 1000 * 60 * 60 * 24 * 7 * 8
|
||||
const eightWeeksAgo = nowMs - eightWeeks
|
||||
if (thisOffer.timestamp < eightWeeksAgo) {
|
||||
console.log('Offer older than 8 weeks. Deleting.')
|
||||
await thisOffer.remove()
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error in removeStaleOffers(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OfferUseCases
|
||||
|
||||
+86
-9
@@ -28,8 +28,24 @@ class OrderLib {
|
||||
async createOrder (entryObj) {
|
||||
try {
|
||||
console.log('createOrder(entryObj): ', entryObj)
|
||||
if (!entryObj) return false
|
||||
|
||||
if (!entryObj.tokenId) throw new Error('entry does not contain required properties')
|
||||
|
||||
// Specify the address to send payment.
|
||||
entryObj.makerAddr = this.adapters.wallet.bchWallet.walletInfo.cashAddress
|
||||
console.log('entryObj.makerAddr: ', entryObj.makerAddr)
|
||||
|
||||
// Get Ticker for token ID.
|
||||
// TODO: Move this below the orderEntity.validate() call.
|
||||
const tokenData = await this.adapters.wallet.bchWallet.getTxData([entryObj.tokenId])
|
||||
// console.log(`tokenData: ${JSON.stringify(tokenData, null, 2)}`)
|
||||
entryObj.ticker = tokenData[0].tokenTicker
|
||||
|
||||
// Input Validation
|
||||
// TODO: Remove ticker from validation.
|
||||
// TODO: Rename validate() to inputValidate(). Create fullValidate() that
|
||||
// validates a fully hydrated Order entity.
|
||||
const orderEntity = this.orderEntity.validate(entryObj)
|
||||
console.log('orderEntity: ', orderEntity)
|
||||
|
||||
@@ -51,10 +67,7 @@ class OrderLib {
|
||||
// Update the order with the new UTXO information.
|
||||
orderEntity.utxoTxid = utxoInfo.txid
|
||||
orderEntity.utxoVout = utxoInfo.vout
|
||||
|
||||
// Specify the address to send payment.
|
||||
orderEntity.makerAddr = this.adapters.wallet.bchWallet.walletInfo.cashAddress
|
||||
console.log('orderEntity.makerAddr: ', orderEntity.makerAddr)
|
||||
orderEntity.tokenType = utxoInfo.tokenType
|
||||
|
||||
// Add P2WDB specific flag for signaling that this is a new offer.
|
||||
orderEntity.dataType = 'offer'
|
||||
@@ -71,6 +84,7 @@ class OrderLib {
|
||||
// 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)
|
||||
await order.save()
|
||||
@@ -78,7 +92,7 @@ class OrderLib {
|
||||
return hash
|
||||
} catch (err) {
|
||||
// console.log("Error in use-cases/entry.js/createEntry()", err.message)
|
||||
wlogger.error('Error in use-cases/createOrder())')
|
||||
wlogger.error('Error in use-cases/order.js/createOrder())')
|
||||
console.log('error entryObj: ', entryObj)
|
||||
throw err
|
||||
}
|
||||
@@ -91,6 +105,9 @@ class OrderLib {
|
||||
// console.log('this.adapters.wallet: ', this.adapters.wallet.bchWallet)
|
||||
// console.log(`walletInfo: ${JSON.stringify(this.adapters.wallet.bchWallet.walletInfo, null, 2)}`)
|
||||
|
||||
// Update the wallet balance
|
||||
await this.adapters.wallet.bchWallet.getUtxos()
|
||||
|
||||
// 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)
|
||||
@@ -103,11 +120,14 @@ class OrderLib {
|
||||
if (orderEntity.buyOrSell.includes('sell')) {
|
||||
// Sell Order
|
||||
|
||||
// Combine Fungible and NFT token UTXOs.
|
||||
let tokenUtxos = utxos.slpUtxos.type1.tokens.concat(utxos.slpUtxos.nft.tokens)
|
||||
|
||||
// Get token UTXOs that match the token in the order.
|
||||
const tokenUtxos = utxos.slpUtxos.type1.tokens.filter(
|
||||
tokenUtxos = tokenUtxos.filter(
|
||||
x => x.tokenId === orderEntity.tokenId
|
||||
)
|
||||
// console.log('tokenUtxos: ', tokenUtxos)
|
||||
console.log('tokenUtxos: ', tokenUtxos)
|
||||
|
||||
// Get the total amount of tokens in the wallet that match the token
|
||||
// in the order.
|
||||
@@ -117,7 +137,7 @@ class OrderLib {
|
||||
|
||||
// If there are fewer tokens in the wallet than what's in the order,
|
||||
// throw an error.
|
||||
if (totalTokenBalance <= orderEntity.numTokens || isNaN(totalTokenBalance)) {
|
||||
if (totalTokenBalance < orderEntity.numTokens || isNaN(totalTokenBalance)) {
|
||||
throw new Error(
|
||||
'App wallet does not have enough tokens to satisfy the SELL order.'
|
||||
)
|
||||
@@ -154,7 +174,64 @@ class OrderLib {
|
||||
|
||||
return orderObject
|
||||
} catch (err) {
|
||||
console.error('Error in findOrder(): ', err)
|
||||
console.error('Error in findOrder()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// This function is called by the garbage collection timer controller. It
|
||||
// checks the UTXO associated with each Order in the database. If the UTXO
|
||||
// has been spent, the Order is deleted from the database.
|
||||
async removeStaleOrders () {
|
||||
try {
|
||||
const now = new Date()
|
||||
console.log(`Starting garbage collection for Orders at ${now.toLocaleString()}`)
|
||||
|
||||
// Get all Orders in the database.
|
||||
const orders = await this.OrderModel.find({})
|
||||
// console.log('orders: ', orders)
|
||||
|
||||
// Loop through each Order and ensure the UTXO is still valid.
|
||||
for (let i = 0; i < orders.length; i++) {
|
||||
const thisOrder = orders[i]
|
||||
|
||||
let utxoStatus = null
|
||||
try {
|
||||
// Get the status of the UTXO associate with this Order.
|
||||
// utxoStatus = await this.adapters.bchjs.Blockchain.getTxOut(
|
||||
// thisOrder.utxoTxid,
|
||||
// thisOrder.utxoVout
|
||||
// )
|
||||
|
||||
// Get the status of the UTXO associate with this Order.
|
||||
const utxo = {
|
||||
tx_hash: thisOrder.utxoTxid,
|
||||
tx_pos: thisOrder.utxoVout
|
||||
}
|
||||
utxoStatus = await this.adapters.wallet.bchWallet.utxoIsValid(utxo)
|
||||
// console.log('utxoStatus: ', utxoStatus)
|
||||
} catch (err) {
|
||||
// Handle corner case of bad-data in the Order model.
|
||||
if (err.message && err.message.includes('txid needs to be a proper transaction ID')) {
|
||||
console.log(`Deleting Order with bad data: ${JSON.stringify(thisOrder, null, 2)}`)
|
||||
await thisOrder.remove()
|
||||
continue
|
||||
} else if (err.isAxiosError) {
|
||||
console.log('Error trying to contact wallet service: ', err)
|
||||
continue
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// If the Order UTXO is spent, delete the Order model.
|
||||
if (!utxoStatus) {
|
||||
console.log(`Spent UTXO detected. Deleting this Order: ${JSON.stringify(thisOrder, null, 2)}`)
|
||||
await thisOrder.remove()
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error in removeStaleOrders()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
// Public npm libraries
|
||||
const assert = require('chai').assert
|
||||
const axios = require('axios').default
|
||||
// const sinon = require('sinon')
|
||||
|
||||
// Local support libraries
|
||||
const config = require('../../../config')
|
||||
|
||||
@@ -12,7 +12,7 @@ async function start () {
|
||||
method: 'post',
|
||||
url: `${LOCALHOST}/offer/take`,
|
||||
data: {
|
||||
offerCid: 'zdpuAwVTq7nF19VaDw6n49S2RvsVHNkYufZNP515Pqocyae9m'
|
||||
offerCid: 'zdpuAkv76xbgqFEdFvcz28yNvtVjoZS7wsMo5wvuxWstycLRg'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/*
|
||||
Unit tests for the adapters index.js library
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
|
||||
// Local libraries
|
||||
const Adapters = require('../../../src/adapters')
|
||||
|
||||
describe('#adapters', () => {
|
||||
let uut, sandbox
|
||||
|
||||
beforeEach(() => {
|
||||
uut = new Adapters()
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
|
||||
afterEach(() => {
|
||||
sandbox.restore()
|
||||
})
|
||||
|
||||
describe('#start', () => {
|
||||
it('should start the async adapters', async () => {
|
||||
// Mock dependencies
|
||||
uut.config.getJwtAtStartup = true
|
||||
sandbox.stub(uut.fullStackJwt, 'getJWT').resolves()
|
||||
sandbox.stub(uut.fullStackJwt, 'instanceBchjs').resolves()
|
||||
sandbox.stub(uut.ipfs, 'start').resolves()
|
||||
|
||||
const result = await uut.start()
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
uut.config.getJwtAtStartup = false
|
||||
// uut.config.env = 'dev'
|
||||
sandbox.stub(uut.wallet, 'openWallet').rejects(new Error('test error'))
|
||||
|
||||
await uut.start()
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -8,6 +8,7 @@ const sinon = require('sinon')
|
||||
const IPFSCoordAdapter = require('../../../src/adapters/ipfs/ipfs-coord')
|
||||
const IPFSMock = require('../mocks/ipfs-mock')
|
||||
const IPFSCoordMock = require('../mocks/ipfs-coord-mock')
|
||||
const config = require('../../../config')
|
||||
|
||||
describe('#IPFS', () => {
|
||||
let uut
|
||||
@@ -61,6 +62,16 @@ describe('#IPFS', () => {
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
it('should return a promise that resolves into an instance of IPFS in production mode', async () => {
|
||||
uut.config.isProduction = true
|
||||
// Mock dependencies.
|
||||
uut.IpfsCoord = IPFSCoordMock
|
||||
|
||||
const result = await uut.start()
|
||||
// console.log('result: ', result)
|
||||
assert.equal(result, true)
|
||||
config.isProduction = false
|
||||
})
|
||||
})
|
||||
|
||||
describe('#attachRPCRouter', () => {
|
||||
@@ -101,4 +112,20 @@ describe('#IPFS', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#subscribeToChat', () => {
|
||||
it('should subscribe to the chat channel', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfsCoord = {
|
||||
adapters: {
|
||||
pubsub: {
|
||||
subscribeToPubsubChannel: async () => {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await uut.subscribeToChat()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
|
||||
const IPFSLib = require('../../../src/adapters/ipfs/ipfs')
|
||||
const IPFSMock = require('../mocks/ipfs-mock')
|
||||
const config = require('../../../config')
|
||||
|
||||
// config.isProduction = true;
|
||||
describe('#IPFS-adapter', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
@@ -18,7 +19,27 @@ describe('#IPFS-adapter', () => {
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
afterEach(() => {
|
||||
sandbox.restore()
|
||||
})
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should instantiate IPFS Lib in dev mode.', async () => {
|
||||
const _uut = new IPFSLib()
|
||||
assert.exists(_uut)
|
||||
assert.isFunction(_uut.start)
|
||||
assert.isFunction(_uut.stop)
|
||||
})
|
||||
|
||||
it('should instantiate dev IPFS Lib in production mode.', async () => {
|
||||
config.isProduction = true
|
||||
const _uut = new IPFSLib()
|
||||
assert.exists(_uut)
|
||||
assert.isFunction(_uut.start)
|
||||
assert.isFunction(_uut.stop)
|
||||
config.isProduction = false
|
||||
})
|
||||
})
|
||||
|
||||
describe('#start', () => {
|
||||
it('should return a promise that resolves into an instance of IPFS.', async () => {
|
||||
@@ -33,6 +54,18 @@ describe('#IPFS-adapter', () => {
|
||||
assert.property(result, 'config')
|
||||
})
|
||||
|
||||
it('should return a promise that resolves into an instance of IPFS in production mode.', async () => {
|
||||
// Mock dependencies.
|
||||
uut.IPFS = IPFSMock
|
||||
uut.config.isProduction = true
|
||||
const result = await uut.start()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(uut.isReady, true)
|
||||
|
||||
assert.property(result, 'config')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
|
||||
@@ -32,19 +32,18 @@ describe('#LogsApiLib', () => {
|
||||
})
|
||||
|
||||
it('should return log', async () => {
|
||||
try {
|
||||
const pass = 'test'
|
||||
const result = await uut.getLogs(pass)
|
||||
// console.log('result', result)
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut, 'generateFileName').returns(`${__dirname.toString()}/../mocks/adapters/fake-log`)
|
||||
|
||||
assert.isTrue(result.success)
|
||||
assert.isArray(result.data)
|
||||
assert.property(result.data[0], 'message')
|
||||
assert.property(result.data[0], 'level')
|
||||
assert.property(result.data[0], 'timestamp')
|
||||
} catch (err) {
|
||||
assert(false, 'Unexpected result')
|
||||
}
|
||||
const pass = 'test'
|
||||
const result = await uut.getLogs(pass)
|
||||
// console.log('result', result)
|
||||
|
||||
assert.isTrue(result.success)
|
||||
assert.isArray(result.data)
|
||||
assert.property(result.data[0], 'message')
|
||||
assert.property(result.data[0], 'level')
|
||||
assert.property(result.data[0], 'timestamp')
|
||||
})
|
||||
|
||||
it('should return false if files are not found!', async () => {
|
||||
@@ -201,31 +200,25 @@ describe('#LogsApiLib', () => {
|
||||
})
|
||||
|
||||
it('should ignore fileReader callback errors', async () => {
|
||||
try {
|
||||
// https://sinonjs.org/releases/latest/stubs/
|
||||
// About yields
|
||||
sandbox.stub(uut.lineReader, 'eachLine').yieldsRight({}, true)
|
||||
// https://sinonjs.org/releases/latest/stubs/
|
||||
// About yields
|
||||
sandbox.stub(uut.lineReader, 'eachLine').yieldsRight({}, true)
|
||||
|
||||
const fileName = context.fileName
|
||||
const result = await uut.readLines(fileName)
|
||||
assert.isArray(result)
|
||||
} catch (err) {
|
||||
assert.fail('Unexpected result')
|
||||
}
|
||||
const fileName = `${__dirname.toString()}/../mocks/adapters/fake-log`
|
||||
|
||||
const result = await uut.readLines(fileName)
|
||||
assert.isArray(result)
|
||||
})
|
||||
|
||||
it('should return data', async () => {
|
||||
try {
|
||||
const fileName = context.fileName
|
||||
const result = await uut.readLines(fileName)
|
||||
const fileName = `${__dirname.toString()}/../mocks/adapters/fake-log`
|
||||
|
||||
assert.isArray(result)
|
||||
assert.property(result[1], 'message')
|
||||
assert.property(result[1], 'level')
|
||||
assert.property(result[1], 'timestamp')
|
||||
} catch (err) {
|
||||
assert.fail('Unexpected result')
|
||||
}
|
||||
const result = await uut.readLines(fileName)
|
||||
|
||||
assert.isArray(result)
|
||||
assert.property(result[1], 'message')
|
||||
assert.property(result[1], 'level')
|
||||
assert.property(result[1], 'timestamp')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -5,31 +5,34 @@
|
||||
// Public npm libraries.
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
const BCHJS = require('@psf/bch-js')
|
||||
// const BCHJS = require('@psf/bch-js')
|
||||
const BchWallet = require('minimal-slp-wallet/index.js')
|
||||
|
||||
// Local libraries.
|
||||
const P2wdbAdapter = require('../../../src/adapters/p2wdb-adapter')
|
||||
|
||||
describe('#P2wdbAdapter', () => {
|
||||
let uut, sandbox
|
||||
let uut, sandbox, bchWallet
|
||||
|
||||
beforeEach(() => {
|
||||
const bchjs = new BCHJS()
|
||||
beforeEach(async () => {
|
||||
// const bchjs = new BCHJS()
|
||||
bchWallet = new BchWallet()
|
||||
await bchWallet.walletInfoPromise
|
||||
|
||||
uut = new P2wdbAdapter({ bchjs })
|
||||
uut = new P2wdbAdapter({ bchWallet })
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should throw error if bch-js is not passed in', () => {
|
||||
it('should throw error if bchWallet 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.')
|
||||
assert.include(err.message, 'Must pass an instance of minimal-slp-wallet as bchWallet when instantiating p2wdb.js adapter.')
|
||||
}
|
||||
})
|
||||
})
|
||||
@@ -82,7 +85,7 @@ describe('#P2wdbAdapter', () => {
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(err.message, 'WIF private key required')
|
||||
assert.include(err.message, 'WIF input required when calling instantiateWriteLib()')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
@@ -99,20 +99,20 @@ describe('#wallet', () => {
|
||||
})
|
||||
|
||||
describe('#instanceWallet', () => {
|
||||
it('should create an instance of BchWallet', async () => {
|
||||
// Mock dependencies
|
||||
uut.BchWallet = MockBchWallet
|
||||
|
||||
// Ensure we open the test file, not the production wallet file.
|
||||
uut.WALLET_FILE = testWalletFile
|
||||
|
||||
const walletData = await uut.openWallet()
|
||||
|
||||
const result = await uut.instanceWallet(walletData.mnemonic)
|
||||
console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'walletInfoPromise')
|
||||
})
|
||||
// it('should create an instance of BchWallet', async () => {
|
||||
// // Mock dependencies
|
||||
// uut.BchWallet = MockBchWallet
|
||||
//
|
||||
// // Ensure we open the test file, not the production wallet file.
|
||||
// uut.WALLET_FILE = testWalletFile
|
||||
//
|
||||
// const walletData = await uut.openWallet()
|
||||
//
|
||||
// const result = await uut.instanceWallet(walletData.mnemonic)
|
||||
// console.log('result: ', result)
|
||||
//
|
||||
// assert.property(result, 'walletInfoPromise')
|
||||
// })
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
@@ -262,10 +262,11 @@ describe('#wallet', () => {
|
||||
wif: 'L4qKTMCwjH9jHnYtNh9Vsrxj7Hg6zmoN8E2v7N47UKvNVEjw7FU8',
|
||||
hdIndex: 6
|
||||
})
|
||||
uut.bchWallet = {
|
||||
sendTokens: async () => 'fake-txid',
|
||||
getUtxos: async () => {}
|
||||
}
|
||||
uut.bchWallet = new MockBchWallet()
|
||||
// uut.bchWallet = {
|
||||
// sendTokens: async () => 'fake-txid',
|
||||
// getUtxos: async () => {}
|
||||
// }
|
||||
|
||||
const inObj = {
|
||||
tokenId: 'a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2',
|
||||
|
||||
@@ -253,12 +253,15 @@ describe('#Offer-Entity', () => {
|
||||
'241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
|
||||
utxoVout: 0,
|
||||
offerStatus: 'posted',
|
||||
makerAddr: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00'
|
||||
makerAddr: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
|
||||
ticker: 'TROUT',
|
||||
tokenType: 1
|
||||
},
|
||||
timestamp: '2021-09-20T17:54:26.395Z',
|
||||
localTimeStamp: '9/20/2021, 10:54:26 AM',
|
||||
txid: '46f50f2a0cf44e3ed70dfb0618ef3ebfee57aabcf229b5d2d17c07322b54a8d7',
|
||||
hash: 'zdpuB2X25AZCKo3wpr4sSbw44vqPWJRqcxWQRHZccK5BdtoGD'
|
||||
|
||||
}
|
||||
|
||||
const result = uut.validate(offerObj)
|
||||
@@ -277,6 +280,7 @@ describe('#Offer-Entity', () => {
|
||||
assert.property(result, 'localTimestamp')
|
||||
assert.property(result, 'txid')
|
||||
assert.property(result, 'p2wdbHash')
|
||||
assert.property(result, 'tokenType')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
Unit tests for the bin/server.js file
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
|
||||
// Local libraries
|
||||
const Server = require('../../../bin/server')
|
||||
|
||||
describe('#server', () => {
|
||||
let uut, sandbox
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
uut = new Server()
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#startServer', () => {
|
||||
it('should start the server', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.mongoose, 'connect').resolves()
|
||||
sandbox.stub(uut.controllers, 'initAdapters').resolves()
|
||||
sandbox.stub(uut.controllers, 'initUseCases').resolves()
|
||||
sandbox.stub(uut.controllers, 'attachRESTControllers').resolves()
|
||||
sandbox.stub(uut.adminLib, 'createSystemUser').resolves(true)
|
||||
sandbox.stub(uut.controllers, 'attachControllers').resolves()
|
||||
sandbox.stub(uut, 'sleep').resolves()
|
||||
uut.config.env = 'dev'
|
||||
|
||||
const result = await uut.startServer()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'env')
|
||||
|
||||
// Turn off the server.
|
||||
uut.server.close()
|
||||
|
||||
// Restor config env
|
||||
uut.config.env = 'test'
|
||||
})
|
||||
|
||||
it('should exit on failure', async () => {
|
||||
// Force an error
|
||||
sandbox.stub(uut.mongoose, 'connect').rejects(new Error('test error'))
|
||||
|
||||
// Prevent default behavior of exiting the program.
|
||||
sandbox.stub(uut, 'sleep').resolves()
|
||||
sandbox.stub(uut.process, 'exit').returns()
|
||||
|
||||
await uut.startServer()
|
||||
|
||||
// Not throwing an error is a success
|
||||
})
|
||||
})
|
||||
|
||||
describe('#sleep', () => {
|
||||
it('should execute', async () => {
|
||||
await uut.sleep(1)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,70 @@
|
||||
{"level":"error","message":"Error in lib/contact.js/sendEmail()","timestamp":"2022-06-27T17:36:43.907Z"}
|
||||
{"level":"error","message":"Error in lib/contact.js/sendEmail()","timestamp":"2022-06-27T17:36:43.909Z"}
|
||||
{"level":"error","message":"Error in lib/contact.js/sendEmail()","timestamp":"2022-06-27T17:36:43.910Z"}
|
||||
{"level":"error","message":"Error in lib/contact.js/sendEmail()","timestamp":"2022-06-27T17:36:43.912Z"}
|
||||
{"level":"error","message":"Error in lib/contact.js/sendEmail()","timestamp":"2022-06-27T17:36:43.915Z"}
|
||||
{"level":"error","message":"Error in lib/nodemailer.js/sendEmail()","timestamp":"2022-06-27T17:36:58.979Z"}
|
||||
{"level":"error","message":"Error in lib/nodemailer.js/sendEmail()","timestamp":"2022-06-27T17:36:58.980Z"}
|
||||
{"level":"error","message":"Error in lib/nodemailer.js/sendEmail()","timestamp":"2022-06-27T17:36:58.981Z"}
|
||||
{"level":"error","message":"Error in lib/nodemailer.js/sendEmail()","timestamp":"2022-06-27T17:36:58.982Z"}
|
||||
{"level":"error","message":"Error in lib/nodemailer.js/sendEmail()","timestamp":"2022-06-27T17:36:58.983Z"}
|
||||
{"level":"error","message":"Error in lib/nodemailer.js/validateEmailArray()","timestamp":"2022-06-27T17:36:59.002Z"}
|
||||
{"level":"error","message":"Error in lib/nodemailer.js/validateEmailArray()","timestamp":"2022-06-27T17:36:59.003Z"}
|
||||
{"level":"error","message":"Error in lib/nodemailer.js/getHtmlFromObject()","timestamp":"2022-06-27T17:36:59.005Z"}
|
||||
{"level":"error","message":"Error in lib/nodemailer.js/getHtmlFromObject()","timestamp":"2022-06-27T17:36:59.006Z"}
|
||||
{"level":"error","message":"Error in lib/nodemailer.js/getHtmlFromObject()","timestamp":"2022-06-27T17:36:59.008Z"}
|
||||
{"level":"info","message":"Warning: Can not send JSON RPC response. Can not determine which peer this message came from.","timestamp":"2022-06-27T17:36:59.353Z"}
|
||||
{"level":"info","message":"Rejecting invalid JSON RPC command.","timestamp":"2022-06-27T17:36:59.354Z"}
|
||||
{"level":"info","message":"JSON RPC received from peerA, ID: ea442e78-2297-466a-823b-9cbb12c8ddd9, type: request, method: unknownMethod","timestamp":"2022-06-27T17:36:59.355Z"}
|
||||
{"level":"error","message":"Error in rpc router(): test error","stack":"Error: test error\n at Context.<anonymous> (/home/trout/work/psf/code/ipfs-service-provider/test/unit/controllers/json-rpc/a10-rpc.unit.js:110:49)\n at callFn (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runnable.js:366:21)\n at Test.Runnable.run (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runnable.js:354:5)\n at Runner.runTest (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:666:10)\n at /home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:789:12\n at next (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:581:14)\n at /home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:591:7\n at next (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:474:14)\n at Immediate._onImmediate (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:559:5)\n at processImmediate (node:internal/timers:466:21)","timestamp":"2022-06-27T17:36:59.357Z"}
|
||||
{"level":"info","message":"JSON RPC received from peerA, ID: 9ef6ad90-7fc3-4cb3-8769-4498e5512b3d, type: request, method: users","timestamp":"2022-06-27T17:36:59.358Z"}
|
||||
{"level":"info","message":"JSON RPC received from peerA, ID: bd57a1cf-7f2f-4ff2-93d4-6727eec4d778, type: request, method: auth","timestamp":"2022-06-27T17:36:59.359Z"}
|
||||
{"level":"info","message":"JSON RPC received from peerA, ID: 8fd9edc8-6a45-4042-ac06-9ace60567cbf, type: request, method: about","timestamp":"2022-06-27T17:36:59.361Z"}
|
||||
{"level":"info","message":"JSON RPC received from peerA, ID: 1b27624a-e698-4bbe-9183-b860a1a0d78d, type: request, method: unknownMethod","timestamp":"2022-06-27T17:36:59.363Z"}
|
||||
{"level":"info","message":"JSON RPC received from peerA, ID: 40fabddf-59c6-4910-a22c-63da7387f99a, type: request, method: unknownMethod","timestamp":"2022-06-27T17:36:59.365Z"}
|
||||
{"level":"error","message":"Error in authUser(): Login credential do not match","stack":"Error: Login credential do not match\n at Context.<anonymous> (/home/trout/work/psf/code/ipfs-service-provider/test/unit/controllers/json-rpc/auth.json-rpc.controller.unit.js:144:18)\n at callFn (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runnable.js:366:21)\n at Test.Runnable.run (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runnable.js:354:5)\n at Runner.runTest (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:666:10)\n at /home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:789:12\n at next (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:581:14)\n at /home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:591:7\n at next (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:474:14)\n at Immediate.<anonymous> (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:559:5)\n at processImmediate (node:internal/timers:466:21)","timestamp":"2022-06-27T17:37:04.421Z"}
|
||||
{"level":"error","message":"Error in authUser(): login must be specified","stack":"Error: login must be specified\n at AuthRPC.authUser (/home/trout/work/psf/code/ipfs-service-provider/src/controllers/json-rpc/auth/index.js:78:93)\n at Context.<anonymous> (/home/trout/work/psf/code/ipfs-service-provider/test/unit/controllers/json-rpc/auth.json-rpc.controller.unit.js:165:34)\n at callFn (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runnable.js:366:21)\n at Test.Runnable.run (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runnable.js:354:5)\n at Runner.runTest (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:666:10)\n at /home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:789:12\n at next (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:581:14)\n at /home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:591:7\n at next (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:474:14)\n at Immediate._onImmediate (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:559:5)\n at processImmediate (node:internal/timers:466:21)","timestamp":"2022-06-27T17:37:04.423Z"}
|
||||
{"level":"error","message":"Error in authUser(): password must be specified","stack":"Error: password must be specified\n at AuthRPC.authUser (/home/trout/work/psf/code/ipfs-service-provider/src/controllers/json-rpc/auth/index.js:78:284)\n at Context.<anonymous> (/home/trout/work/psf/code/ipfs-service-provider/test/unit/controllers/json-rpc/auth.json-rpc.controller.unit.js:185:34)\n at callFn (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runnable.js:366:21)\n at Test.Runnable.run (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runnable.js:354:5)\n at Runner.runTest (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:666:10)\n at /home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:789:12\n at next (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:581:14)\n at /home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:591:7\n at next (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:474:14)\n at Immediate._onImmediate (/home/trout/work/psf/code/ipfs-service-provider/node_modules/mocha/lib/runner.js:559:5)\n at processImmediate (node:internal/timers:466:21)","timestamp":"2022-06-27T17:37:04.425Z"}
|
||||
{"level":"error","timestamp":"2022-06-27T17:37:04.466Z"}
|
||||
{"level":"info","message":"Running server in environment: dev","timestamp":"2022-06-27T17:37:04.487Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:04.495Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:04.495Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:04.496Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:04.497Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:04.497Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/getAllUsers()","timestamp":"2022-06-27T17:37:04.499Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:04.502Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:04.503Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:04.503Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:04.504Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:04.505Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:04.506Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/deleteUser()","timestamp":"2022-06-27T17:37:04.508Z"}
|
||||
{"level":"info","message":"Running server in environment: test","timestamp":"2022-06-27T17:37:04.514Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:04.638Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:05.273Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:05.277Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:05.280Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:05.286Z"}
|
||||
{"level":"error","timestamp":"2022-06-27T17:37:05.412Z"}
|
||||
{"level":"verbose","message":"Calling user and target user do not match! Calling user: 62b9eac1b4f08950b955d27f, Target user: 1","timestamp":"2022-06-27T17:37:05.441Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:05.448Z"}
|
||||
{"level":"verbose","message":"Calling user and target user do not match! Calling user: 62b9eac1b4f08950b955d27f, Target user: 62b9eac1b4f08950b955d27d","timestamp":"2022-06-27T17:37:05.452Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:05.457Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:05.463Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:05.467Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/updateUser()","timestamp":"2022-06-27T17:37:05.473Z"}
|
||||
{"level":"verbose","message":"Calling user and target user do not match! Calling user: 62b9eac0b4f08950b955d274, Target user: 62b9eac1b4f08950b955d27d","timestamp":"2022-06-27T17:37:05.478Z"}
|
||||
{"level":"verbose","message":"It's ok. The user is an admin.","timestamp":"2022-06-27T17:37:05.478Z"}
|
||||
{"level":"verbose","message":"Calling user and target user do not match! Calling user: 62b9eac1b4f08950b955d27f, Target user: 1","timestamp":"2022-06-27T17:37:05.600Z"}
|
||||
{"level":"verbose","message":"Calling user and target user do not match! Calling user: 62b9eac1b4f08950b955d27f, Target user: 62b9eac1b4f08950b955d27d","timestamp":"2022-06-27T17:37:05.603Z"}
|
||||
{"level":"verbose","message":"Calling user and target user do not match! Calling user: 62b9eac0b4f08950b955d274, Target user: 62b9eac1b4f08950b955d27d","timestamp":"2022-06-27T17:37:05.615Z"}
|
||||
{"level":"verbose","message":"It's ok. The user is an admin.","timestamp":"2022-06-27T17:37:05.615Z"}
|
||||
{"level":"error","message":"Error in lib/contact.js/sendEmail()","timestamp":"2022-06-27T17:37:05.619Z"}
|
||||
{"level":"error","message":"Error in lib/contact.js/sendEmail()","timestamp":"2022-06-27T17:37:05.622Z"}
|
||||
{"level":"error","message":"Error in lib/contact.js/sendEmail()","timestamp":"2022-06-27T17:37:05.624Z"}
|
||||
{"level":"error","message":"Error in lib/contact.js/sendEmail()","timestamp":"2022-06-27T17:37:05.627Z"}
|
||||
{"level":"verbose","message":"Calling user and target user do not match! Calling user: 62b9eac1b4f08950b955d2a6, Target user: Target Id","timestamp":"2022-06-27T17:37:05.754Z"}
|
||||
{"level":"verbose","message":"Calling user and target user do not match! Calling user: 62b9eac0b4f08950b955d274, Target user: 62b9eac1b4f08950b955d2a6","timestamp":"2022-06-27T17:37:05.756Z"}
|
||||
{"level":"verbose","message":"It's ok. The user is an admin.","timestamp":"2022-06-27T17:37:05.756Z"}
|
||||
{"level":"error","message":"Error in lib/users.js/createUser()","timestamp":"2022-06-27T17:37:06.060Z"}
|
||||
@@ -1,16 +1,22 @@
|
||||
/*
|
||||
Mocks for the Adapter library.
|
||||
*/
|
||||
|
||||
const BCHJS = require('@psf/bch-js')
|
||||
const bchjs = new BCHJS()
|
||||
|
||||
const ipfs = {
|
||||
ipfsAdapter: {
|
||||
ipfs: {}
|
||||
},
|
||||
ipfsCoordAdapter: {
|
||||
ipfsCoord: {
|
||||
class IpfsAdapter {
|
||||
constructor () {
|
||||
this.ipfs = {
|
||||
files: {
|
||||
stat: () => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class IpfsCoordAdapter {
|
||||
constructor () {
|
||||
this.ipfsCoord = {
|
||||
useCases: {
|
||||
peer: {
|
||||
sendPrivateMessage: () => {
|
||||
@@ -21,6 +27,12 @@ const ipfs = {
|
||||
}
|
||||
}
|
||||
|
||||
const ipfs = {
|
||||
ipfsAdapter: new IpfsAdapter(),
|
||||
ipfsCoordAdapter: new IpfsCoordAdapter()
|
||||
}
|
||||
ipfs.ipfs = ipfs.ipfsAdapter.ipfs
|
||||
|
||||
const localdb = {
|
||||
Users: class Users {
|
||||
static findById () {}
|
||||
|
||||
@@ -29,6 +29,11 @@ class MockBchWallet {
|
||||
}
|
||||
this.getUtxos = async () => {
|
||||
}
|
||||
this.getTxData = async () => {
|
||||
return [{
|
||||
tokenTicker: 'TROUT'
|
||||
}]
|
||||
}
|
||||
|
||||
// Environment variable is used by wallet-balance.unit.js to force an error.
|
||||
if (process.env.NO_UTXO) {
|
||||
@@ -122,9 +127,11 @@ class MockBchWallet {
|
||||
]
|
||||
},
|
||||
nft: {
|
||||
groupMintBatons: [],
|
||||
groupTokens: [],
|
||||
tokens: []
|
||||
},
|
||||
group: {
|
||||
tokens: [],
|
||||
mintBatons: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ class IPFSCoord {
|
||||
}
|
||||
|
||||
async start () {}
|
||||
|
||||
async subscribeToChat() {}
|
||||
}
|
||||
|
||||
module.exports = IPFSCoord
|
||||
|
||||
@@ -47,4 +47,17 @@ describe('#use-cases', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#start', () => {
|
||||
it('should initialize async use cases', async () => {
|
||||
const result = await uut.start()
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
// it('should catch and throw errors', async () => {
|
||||
// // Force an error
|
||||
// sandbox.stub()
|
||||
// })
|
||||
})
|
||||
})
|
||||
|
||||
@@ -95,7 +95,9 @@ describe('#offer-use-case', () => {
|
||||
utxoTxid:
|
||||
'241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
|
||||
utxoVout: 0,
|
||||
makerAddr: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00'
|
||||
makerAddr: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
|
||||
ticker: 'TROUT',
|
||||
tokenType: 1
|
||||
},
|
||||
timestamp: '2021-09-20T17:54:26.395Z',
|
||||
localTimeStamp: '9/20/2021, 10:54:26 AM',
|
||||
|
||||
@@ -109,13 +109,12 @@ describe('#order-use-case', () => {
|
||||
|
||||
// Mock dependencies
|
||||
// sandbox.stub(uut.adapters.wallet, 'burnPsf').resolves('fakeTxid')
|
||||
// sandbox.stub(uut.adapters.wallet.bchWallet, 'getTxData').resolves({ tokenTicker: 'TROUT' })
|
||||
sandbox.stub(uut.orderEntity, 'validate').returns(entryObj)
|
||||
sandbox.stub(uut, 'ensureFunds').resolves()
|
||||
sandbox.stub(uut.adapters.wallet.bchWallet.bchjs.Util, 'sleep').resolves()
|
||||
sandbox.stub(uut.adapters.wallet, 'moveTokens').resolves({ txid: 'fakeTxid', vout: 0, hdIndex: 1 })
|
||||
sandbox.stub(uut.adapters.wallet.bchWallet, 'getUtxos').resolves()
|
||||
// sandbox
|
||||
// .stub(uut.adapters.wallet, 'generateSignature')
|
||||
// .resolves('fakeSignature')
|
||||
sandbox.stub(uut.adapters.p2wdb, 'write').resolves('fakeHash')
|
||||
|
||||
const result = await uut.createOrder(entryObj)
|
||||
@@ -126,17 +125,18 @@ describe('#order-use-case', () => {
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.orderEntity, 'validate')
|
||||
.throws(new Error('test error'))
|
||||
|
||||
await uut.createOrder()
|
||||
await uut.createOrder({ a: 'b' })
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
assert.include(err.message, 'entry does not contain required properties')
|
||||
}
|
||||
})
|
||||
|
||||
it('should exit quietly if the entry is empty', async () => {
|
||||
const result = await uut.createOrder()
|
||||
|
||||
assert.equal(result, false)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -14,7 +14,7 @@ const BchTokenSweep = require('bch-token-sweep/index')
|
||||
const WalletAdapter = require('../../src/adapters/wallet')
|
||||
|
||||
// Constants
|
||||
const EMTPY_ADDR_CUTOFF = 5
|
||||
const EMTPY_ADDR_CUTOFF = 15
|
||||
|
||||
async function sweepFunds () {
|
||||
try {
|
||||
|
||||
Reference in New Issue
Block a user