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 | |
|---|---|---|---|
|
|
b26160b748 | ||
|
|
04f45eea6a | ||
|
|
9096db1614 | ||
|
|
369415bf8d | ||
|
|
7cb536d312 | ||
|
|
592ea60892 | ||
|
|
d14270c5b0 | ||
|
|
2d9a1d7cbf | ||
|
|
e6c942d532 | ||
|
|
1206b6576e | ||
|
|
dbd5845e3a | ||
|
|
d14dcae6f5 | ||
|
|
8154dd96f9 | ||
|
|
e37223cc88 | ||
|
|
5002ef2b90 | ||
|
|
5d95109ad4 | ||
|
|
13b3150f62 | ||
|
|
b3a4a7c1ab | ||
|
|
733e8fd405 | ||
|
|
baee72c1c6 | ||
|
|
11618a796f | ||
|
|
025ae20704 |
+2
-2
@@ -94,13 +94,13 @@ class Server {
|
||||
try {
|
||||
try {
|
||||
// Delete an old webhook if it exists.
|
||||
await webhookLib.deleteWebhook(`http://localhost:${config.port}/p2wdb`)
|
||||
await webhookLib.deleteWebhook(config.webhookTarget)
|
||||
} catch (err) {
|
||||
/* exit quietly */
|
||||
// console.log('err deleting webhook: ', err)
|
||||
}
|
||||
|
||||
await webhookLib.createWebhook(`http://localhost:${config.port}/p2wdb`)
|
||||
await webhookLib.createWebhook(config.webhookTarget)
|
||||
console.log('Webhook created')
|
||||
} catch (error) {
|
||||
console.log('Webhook cant be created')
|
||||
|
||||
Vendored
+4
-1
@@ -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-test557',
|
||||
webhookTarget: process.env.WEBHOOKTARGET
|
||||
? process.env.WEBHOOKTARGET
|
||||
: 'http://localhost:5700/order',
|
||||
|
||||
// IPFS Ports
|
||||
ipfsTcpPort: process.env.IPFS_TCP_PORT ? process.env.IPFS_TCP_PORT : 4001,
|
||||
|
||||
Generated
+51
-1166
File diff suppressed because it is too large
Load Diff
@@ -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_14.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
|
||||
|
||||
# Switch to the desired branch. `master` is usually stable,
|
||||
# and `stage` has the most up-to-date changes.
|
||||
WORKDIR /home/safeuser/bch-dex-ui
|
||||
|
||||
# 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/public /usr/share/nginx/html
|
||||
|
||||
#USER safeuser
|
||||
@@ -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,10 +63,13 @@ 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
|
||||
+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_ENV=production
|
||||
export WEBHOOKSERVICE=http://172.17.0.1:5667/webhook
|
||||
export WEBHOOKTARGET=http://172.17.0.1:5700/order
|
||||
export APP_ID=bch-dex-test557
|
||||
|
||||
npm start
|
||||
@@ -3,18 +3,18 @@
|
||||
version: '3.9'
|
||||
|
||||
services:
|
||||
mongo-ipfs-service:
|
||||
mongo-p2wdb-service:
|
||||
image: mongo
|
||||
container_name: mongo-ipfs-service
|
||||
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
|
||||
image: ipfs/go-ipfs:v0.12.0
|
||||
container_name: ipfs
|
||||
environment:
|
||||
MY_ENV_VAR: 'placeholder'
|
||||
@@ -42,9 +42,53 @@ services:
|
||||
]
|
||||
restart: always
|
||||
|
||||
ipfs-service:
|
||||
build: .
|
||||
container_name: ipfs-service
|
||||
p2wdb:
|
||||
build:
|
||||
context: ./p2wdb/
|
||||
dockerfile: Dockerfile
|
||||
container_name: p2wdb
|
||||
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
|
||||
restart: always
|
||||
|
||||
bch-dex:
|
||||
build:
|
||||
context: ./bch-dex/
|
||||
dockerfile: Dockerfile
|
||||
container_name: bch-dex
|
||||
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:
|
||||
# - ../data/ipfsdata:/home/safeuser/ipfs-service-provider/.ipfsdata
|
||||
#restart: always
|
||||
|
||||
dex-ui:
|
||||
build:
|
||||
context: ./bch-dex-ui/
|
||||
dockerfile: Dockerfile
|
||||
container_name: dex-ui
|
||||
logging:
|
||||
driver: 'json-file'
|
||||
options:
|
||||
@@ -52,10 +96,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
|
||||
restart: always
|
||||
- '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)
|
||||
+16
-8
@@ -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
|
||||
@@ -19,20 +18,29 @@ export COORD_NAME=ipfs-service-provider-generic
|
||||
# Debug level. 0 = minimal info. 2 = max info.
|
||||
export DEBUG_LEVEL=2
|
||||
|
||||
# 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,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: 1
|
||||
}
|
||||
|
||||
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()
|
||||
+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,7 @@ const Offer = new mongoose.Schema({
|
||||
tokenId: { type: String },
|
||||
utxoTxid: { type: String },
|
||||
utxoVout: { type: Number },
|
||||
ticker: { type: String },
|
||||
|
||||
// Trade data
|
||||
buyOrSell: { type: String },
|
||||
|
||||
@@ -13,6 +13,7 @@ const Order = new mongoose.Schema({
|
||||
tokenId: { type: String },
|
||||
utxoTxid: { type: String },
|
||||
utxoVout: { type: Number },
|
||||
ticker: { type: String },
|
||||
|
||||
// Trade data
|
||||
buyOrSell: { type: String },
|
||||
|
||||
@@ -6,18 +6,12 @@
|
||||
|
||||
// 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 = {}) {
|
||||
@@ -33,6 +27,7 @@ class Controllers {
|
||||
// this.attachRESTControllers(app)
|
||||
|
||||
// this.attachRPCControllers()
|
||||
this.timerControllers = new TimerControllers({ adapters: this.adapters, useCases: this.useCases })
|
||||
}
|
||||
|
||||
// Top-level function for this library.
|
||||
|
||||
@@ -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
|
||||
@@ -28,7 +28,8 @@ class OfferEntity {
|
||||
utxoTxid,
|
||||
utxoVout,
|
||||
offerStatus,
|
||||
makerAddr
|
||||
makerAddr,
|
||||
ticker
|
||||
} = offerData.data
|
||||
|
||||
// Input Validation
|
||||
@@ -65,6 +66,9 @@ 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.")
|
||||
}
|
||||
|
||||
const validatedOfferData = {
|
||||
messageType,
|
||||
@@ -81,7 +85,8 @@ class OfferEntity {
|
||||
txid: offerData.txid,
|
||||
p2wdbHash: offerData.hash,
|
||||
offerStatus: offerStatus || this.offerStatus[0],
|
||||
makerAddr
|
||||
makerAddr,
|
||||
ticker
|
||||
}
|
||||
|
||||
return validatedOfferData
|
||||
|
||||
@@ -16,7 +16,8 @@ class Order {
|
||||
rateInBaseUnit,
|
||||
minUnitsToExchange,
|
||||
numTokens,
|
||||
makerAddr
|
||||
makerAddr,
|
||||
ticker
|
||||
} = data
|
||||
|
||||
// Input Validation
|
||||
@@ -44,6 +45,9 @@ class Order {
|
||||
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,
|
||||
@@ -53,7 +57,8 @@ class Order {
|
||||
rateInBaseUnit,
|
||||
minUnitsToExchange,
|
||||
numTokens,
|
||||
makerAddr
|
||||
makerAddr,
|
||||
ticker
|
||||
}
|
||||
|
||||
return offerData
|
||||
|
||||
@@ -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.'
|
||||
)
|
||||
}
|
||||
|
||||
@@ -299,6 +299,53 @@ 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
|
||||
)
|
||||
// console.log('utxoStatus: ', utxoStatus)
|
||||
} catch (err) {
|
||||
// Handle corner case of bad-data in the Offer model.
|
||||
if (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 {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// If the Offer UTXO is spent, delete the Offer model.
|
||||
if (utxoStatus === null) {
|
||||
console.log(`Spent UTXO detected. Deleting this Offer: ${JSON.stringify(thisOffer, null, 2)}`)
|
||||
await thisOffer.remove()
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error in removeStaleOffers()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OfferUseCases
|
||||
|
||||
@@ -33,6 +33,11 @@ class OrderLib {
|
||||
entryObj.makerAddr = this.adapters.wallet.bchWallet.walletInfo.cashAddress
|
||||
console.log('entryObj.makerAddr: ', entryObj.makerAddr)
|
||||
|
||||
// Get Ticker for token ID.
|
||||
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
|
||||
const orderEntity = this.orderEntity.validate(entryObj)
|
||||
console.log('orderEntity: ', orderEntity)
|
||||
@@ -158,6 +163,53 @@ class OrderLib {
|
||||
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
|
||||
)
|
||||
// console.log('utxoStatus: ', utxoStatus)
|
||||
} catch (err) {
|
||||
// Handle corner case of bad-data in the Order model.
|
||||
if (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 {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// If the Order UTXO is spent, delete the Order model.
|
||||
if (utxoStatus === null) {
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OrderLib
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -253,12 +253,14 @@ describe('#Offer-Entity', () => {
|
||||
'241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
|
||||
utxoVout: 0,
|
||||
offerStatus: 'posted',
|
||||
makerAddr: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00'
|
||||
makerAddr: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
|
||||
ticker: 'TROUT'
|
||||
},
|
||||
timestamp: '2021-09-20T17:54:26.395Z',
|
||||
localTimeStamp: '9/20/2021, 10:54:26 AM',
|
||||
txid: '46f50f2a0cf44e3ed70dfb0618ef3ebfee57aabcf229b5d2d17c07322b54a8d7',
|
||||
hash: 'zdpuB2X25AZCKo3wpr4sSbw44vqPWJRqcxWQRHZccK5BdtoGD'
|
||||
|
||||
}
|
||||
|
||||
const result = uut.validate(offerObj)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -95,7 +95,8 @@ describe('#offer-use-case', () => {
|
||||
utxoTxid:
|
||||
'241c06bf61384b8623477e419bf4779edbcc7e3bc862f0f179a9ed2967069b87',
|
||||
utxoVout: 0,
|
||||
makerAddr: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00'
|
||||
makerAddr: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
|
||||
ticker: 'TROUT'
|
||||
},
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user