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 | |
|---|---|---|---|
|
|
98b5e300c0 | ||
|
|
2ea062ae2b | ||
|
|
c6e5eb9dc9 | ||
|
|
46a19b0fb1 | ||
|
|
40cf8cc8de | ||
|
|
ddc70d6fbc | ||
|
|
625b8895b0 | ||
|
|
5fd8d8fcd1 | ||
|
|
153ebc204a | ||
|
|
589c16550c | ||
|
|
49063456b6 | ||
|
|
87eb804cd1 | ||
|
|
23d5118b52 | ||
|
|
94eb342435 | ||
|
|
ac326b8e34 | ||
|
|
164a38ad20 | ||
|
|
3300e0fc80 | ||
|
|
3a4569c33f | ||
|
|
05a0620663 | ||
|
|
af83c06b43 | ||
|
|
e6317c3282 | ||
|
|
ddce40bb52 | ||
|
|
746f9a3488 | ||
|
|
a611580f48 | ||
|
|
07c671ede4 | ||
|
|
7c8d73810d | ||
|
|
9829e8581c | ||
|
|
a12aa5609d | ||
|
|
abf834c5a8 | ||
|
|
7ae557d143 | ||
|
|
5f4b0c8da3 | ||
|
|
6ef197dae3 | ||
|
|
fafa7eecd9 | ||
|
|
5ede934be5 | ||
|
|
8d036ea9ba | ||
|
|
bc6acc846a | ||
|
|
afa050261f | ||
|
|
1715d064fb | ||
|
|
cf2f2af90f | ||
|
|
354321c956 | ||
|
|
af2572a493 | ||
|
|
1a5dadbf35 | ||
|
|
ced7a60f14 | ||
|
|
043c4a8b2b |
@@ -1,14 +1,14 @@
|
||||
# bch-dex
|
||||
|
||||
[](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)
|
||||
[](http://standardjs.com) [](https://github.com/semantic-release/semantic-release)
|
||||
|
||||
`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/).
|
||||
`bch-dex` is a decentralized exchange *protocol* 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. `bch-dex` software is maintained by the [Permissionless Software Foundation](https://psfoundation.info).
|
||||
|
||||
- [High-level Overview of bch-dex](https://youtu.be/LVX8CLi4sHw) (Video)
|
||||
|
||||
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 is a prototype web service that monitors one or more [Nostr relays](https://nostr.com) 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.
|
||||
This repository contains the back end code. The user interface is contained in the [bch-dex-ui-v3](https://github.com/Permissionless-Software-Foundation/bch-dex-ui-v3) repository.
|
||||
|
||||
**Warning**: This repository is under active development. Things will be constantly changing and breaking.
|
||||
|
||||
@@ -19,24 +19,17 @@ This repository contains the back end code. The user interface is contained in t
|
||||
|
||||
## 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)
|
||||
- [Telegram Channel](https://t.me/bch_js_toolkit)
|
||||
|
||||
## 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.
|
||||
- [bch-dex-ui-v3](https://github.com/Permissionless-Software-Foundation/bch-dex-ui-v3) web app and user interface (UI) for bch-dex focused on Sellers of tokens.
|
||||
- [MongoDB](https://www.mongodb.com/) is a database used by bch-dex to store and manage local data, user accounts, etc.
|
||||
|
||||
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 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). 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:
|
||||
Setup instructions:
|
||||
|
||||
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`.
|
||||
@@ -44,32 +37,17 @@ Here are the steps involved in setting up bch-dex:
|
||||
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. Change directory to the `production/docker` folder.
|
||||
1. Pull the Docker images down from Docker Hub: `docker-compose pull`
|
||||
1. Build the core software: `docker-compose build --no-cache`
|
||||
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.
|
||||
bch-dex requires a [Cash Stack](https://cashstack.info) back end 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 servers are provided and can be [viewed here](https://consumers.psfoundation.info/consumers.json). The back end can be changed by setting the `CONSUMER_URL` environment variable in the `docker-compose.yml` file.
|
||||
|
||||
## License
|
||||
|
||||
|
||||
Vendored
+7
-1
@@ -183,5 +183,11 @@ export default {
|
||||
|
||||
// Nostr
|
||||
nostrRelay: process.env.NOSTR_RELAY ? process.env.NOSTR_RELAY : 'wss://nostr-relay.psfoundation.info',
|
||||
nostrTopic: process.env.NOSTR_TOPIC ? process.env.NOSTR_TOPIC : 'bch-dex-test-topic-01'
|
||||
nostrTopic: process.env.NOSTR_TOPIC ? process.env.NOSTR_TOPIC : 'bch-dex-test-topic-02',
|
||||
|
||||
// Account Configuration
|
||||
disableNewAccounts: process.env.DISABLE_NEW_ACCOUNTS ? true : false,
|
||||
|
||||
// Admin password
|
||||
adminPassword: process.env.ADMIN_PASSWORD
|
||||
}
|
||||
|
||||
+26701
File diff suppressed because it is too large
Load Diff
Generated
-14
@@ -9861,20 +9861,6 @@
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/fsevents": {
|
||||
"version": "2.3.3",
|
||||
"resolved": "http://94.130.170.209:4873/fsevents/-/fsevents-2.3.3.tgz",
|
||||
"integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/function-bind": {
|
||||
"version": "1.1.2",
|
||||
"resolved": "http://94.130.170.209:4873/function-bind/-/function-bind-1.1.2.tgz",
|
||||
|
||||
+1
-1
@@ -6,7 +6,7 @@
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"test": "npm run test:all",
|
||||
"test": "npm run test:unit",
|
||||
"test:all": "export BCH_DEX=test && c8 --reporter=text mocha --exit --timeout 15000 --recursive test/unit test/e2e/automated/",
|
||||
"test:unit": "export BCH_DEX=test && mocha --exit --timeout 15000 --recursive test/unit/",
|
||||
"test:e2e:auto": "export BCH_DEX=test && mocha --exit --timeout 30000 test/e2e/automated/",
|
||||
|
||||
@@ -46,11 +46,11 @@ WORKDIR /home/safeuser
|
||||
|
||||
# Clone the rest.bitcoin.com repository
|
||||
WORKDIR /home/safeuser
|
||||
RUN git clone https://github.com/Permissionless-Software-Foundation/bch-dex-ui-v2
|
||||
RUN git clone https://github.com/Permissionless-Software-Foundation/bch-dex-ui-v3
|
||||
|
||||
# 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
|
||||
WORKDIR /home/safeuser/bch-dex-ui-v3
|
||||
|
||||
# For development: switch to unstable branch
|
||||
RUN git checkout ct-unstable
|
||||
@@ -61,13 +61,13 @@ RUN git checkout ct-unstable
|
||||
RUN npm install
|
||||
|
||||
# Build the site
|
||||
RUN CI=true npm run build
|
||||
RUN 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
|
||||
COPY --from=builder /home/safeuser/bch-dex-ui-v3/build /usr/share/nginx/html
|
||||
|
||||
#USER safeuser
|
||||
|
||||
+27
-58
@@ -51,70 +51,38 @@ class Admin {
|
||||
// used by the Listing Manager and test scripts, in order access private API
|
||||
// functions.
|
||||
async createSystemUser () {
|
||||
// Create the system user.
|
||||
try {
|
||||
context.password = _this._randomString(20)
|
||||
|
||||
const options = {
|
||||
method: 'POST',
|
||||
url: `${LOCALHOST}/users`,
|
||||
data: {
|
||||
user: {
|
||||
email: 'system@system.com',
|
||||
password: context.password,
|
||||
name: 'admin'
|
||||
}
|
||||
}
|
||||
const context = {
|
||||
email: 'system@system.com',
|
||||
name: 'admin',
|
||||
password: _this.config.adminPassword || _this._randomString(20),
|
||||
type: 'admin',
|
||||
mnemonic: 'spread ivory oval pioneer flash gap merit nerve simple image entire trouble'
|
||||
}
|
||||
const result = await _this.axios.request(options)
|
||||
// console.log('admin.data: ', result.data)
|
||||
// Check if the user already exists
|
||||
let adminUser = await _this.User.findOne({ email: context.email })
|
||||
|
||||
context.email = result.data.user.email
|
||||
context.id = result.data.user._id
|
||||
context.token = result.data.token
|
||||
if (adminUser) {
|
||||
// Update the password
|
||||
adminUser.password = context.password
|
||||
} else {
|
||||
// Create a new admin user
|
||||
adminUser = new _this.User(context)
|
||||
}
|
||||
// Update context with the new user id and token
|
||||
context.id = adminUser._id
|
||||
context.token = await adminUser.generateToken()
|
||||
|
||||
// Get the mongoDB entry
|
||||
const user = await _this.User.findById(context.id)
|
||||
// Save the user
|
||||
await adminUser.save()
|
||||
|
||||
// Change the user type to admin
|
||||
user.type = 'admin'
|
||||
// console.log(`user created: ${JSON.stringify(user, null, 2)}`)
|
||||
|
||||
// Save the user model.
|
||||
await user.save()
|
||||
|
||||
// console.log(`admin user created: ${JSON.stringify(result.body, null, 2)}`)
|
||||
// console.log(`with password: ${context.password}`)
|
||||
|
||||
// Write out the system user information to a JSON file that external
|
||||
// applications like the Task Manager and the test scripts can access.
|
||||
|
||||
await jsonFiles.writeJSON(context, JSON_PATH)
|
||||
// console.log('context: ', context)
|
||||
// console.log('JSON_PATH: ', JSON_PATH)
|
||||
// Write the user data to the JSON file
|
||||
await _this.jsonFiles.writeJSON(context, JSON_PATH)
|
||||
|
||||
return context
|
||||
} catch (err) {
|
||||
// Handle existing system user.
|
||||
if (err.response.status === 422) {
|
||||
try {
|
||||
// Delete the existing user
|
||||
await _this.deleteExistingSystemUser()
|
||||
|
||||
// Call this function again.
|
||||
return _this.createSystemUser()
|
||||
} catch (err2) {
|
||||
console.error(
|
||||
'Error in admin.js/createSystemUser() while trying generate new system user.'
|
||||
)
|
||||
// process.end(1)
|
||||
throw err2
|
||||
}
|
||||
} else {
|
||||
console.log('Error in admin.js/createSystemUser: ')
|
||||
// process.end(1)
|
||||
throw err
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Error in admin.js/createSystemUser()')
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
@@ -129,6 +97,7 @@ class Admin {
|
||||
})
|
||||
|
||||
await _this.User.deleteOne({ email: 'system@system.com' })
|
||||
return true
|
||||
} catch (err) {
|
||||
console.log('Error in admin.js/deleteExistingSystemUser()')
|
||||
throw err
|
||||
@@ -152,7 +121,7 @@ class Admin {
|
||||
Accept: 'application/json'
|
||||
},
|
||||
data: {
|
||||
email: 'system@system.com',
|
||||
email: existingUser.email,
|
||||
password: existingUser.password
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,20 +10,20 @@ import mongoose from 'mongoose'
|
||||
|
||||
const Order = new mongoose.Schema({
|
||||
// Token data
|
||||
tokenId: { type: String },
|
||||
utxoTxid: { type: String },
|
||||
utxoVout: { type: Number },
|
||||
ticker: { type: String },
|
||||
tokenType: { type: Number },
|
||||
tokenId: { type: String, required: true },
|
||||
utxoTxid: { type: String, required: true },
|
||||
utxoVout: { type: Number, required: true },
|
||||
ticker: { type: String, required: true },
|
||||
tokenType: { type: Number, required: true },
|
||||
|
||||
// Trade data
|
||||
buyOrSell: { type: String },
|
||||
numTokens: { type: Number },
|
||||
rateInBaseUnit: { type: String },
|
||||
buyOrSell: { type: String, required: true },
|
||||
numTokens: { type: Number, required: true },
|
||||
rateInBaseUnit: { type: String, required: true },
|
||||
minUnitsToExchange: { type: String },
|
||||
p2wdbTxid: { type: String },
|
||||
p2wdbHash: { type: String },
|
||||
makerAddr: { type: String },
|
||||
makerAddr: { type: String, required: true },
|
||||
|
||||
// Authentication data
|
||||
signature: { type: String },
|
||||
@@ -32,13 +32,18 @@ const Order = new mongoose.Schema({
|
||||
offerPubKey: { type: String },
|
||||
|
||||
// Wallet Data
|
||||
hdIndex: { type: Number }, // HD index address holding the UTXO for this offer.
|
||||
hdIndex: { type: Number, required: true }, // HD index address holding the UTXO for this offer.
|
||||
|
||||
// SWaP Protocol Properties
|
||||
lokadId: { type: String },
|
||||
messageType: { type: Number },
|
||||
messageClass: { type: Number },
|
||||
nostrEventId: { type: String } // Nostr Event Id.
|
||||
nostrEventId: { type: String, required: true }, // Nostr Event Id.
|
||||
|
||||
// Additional properties found in createOrder
|
||||
dataType: { type: String, required: true },
|
||||
|
||||
userId: { type: String, required: true }
|
||||
})
|
||||
|
||||
export default mongoose.model('order', Order)
|
||||
|
||||
@@ -11,6 +11,7 @@ const User = new mongoose.Schema({
|
||||
name: { type: String },
|
||||
username: { type: String },
|
||||
password: { type: String, required: true },
|
||||
mnemonic: { type: String, required: true },
|
||||
email: {
|
||||
type: String,
|
||||
required: true,
|
||||
|
||||
@@ -20,6 +20,13 @@ class Wallet {
|
||||
|
||||
// Bind 'this' object to all subfunctions
|
||||
this.instanceWalletWithoutInitialization = this.instanceWalletWithoutInitialization.bind(this)
|
||||
this._instanceWallet = this._instanceWallet.bind(this)
|
||||
this.openWallet = this.openWallet.bind(this)
|
||||
this.instanceWallet = this.instanceWallet.bind(this)
|
||||
this.incrementNextAddress = this.incrementNextAddress.bind(this)
|
||||
this.getKeyPair = this.getKeyPair.bind(this)
|
||||
this.optimize = this.optimize.bind(this)
|
||||
this.getBalance = this.getBalance.bind(this)
|
||||
}
|
||||
|
||||
// This is used for initializing the wallet, without waiting to update the wallet
|
||||
|
||||
+95
-6
@@ -1,5 +1,7 @@
|
||||
/*
|
||||
Adapter library for working with a wallet.
|
||||
This library is specific to bch-dex. The other wallet adapter library is
|
||||
inherited from ipfs-service-provider.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
@@ -41,7 +43,18 @@ class WalletAdapter {
|
||||
})
|
||||
|
||||
// Bind the 'this' object
|
||||
this.openWallet = this.openWallet.bind(this)
|
||||
this.instanceWallet = this.instanceWallet.bind(this)
|
||||
this.incrementNextAddress = this.incrementNextAddress.bind(this)
|
||||
this.getKeyPair = this.getKeyPair.bind(this)
|
||||
this.generateSignature = this.generateSignature.bind(this)
|
||||
this.generatePartialTx = this.generatePartialTx.bind(this)
|
||||
this.moveTokens = this.moveTokens.bind(this)
|
||||
this.moveBch = this.moveBch.bind(this)
|
||||
this.deseralizeTx = this.deseralizeTx.bind(this)
|
||||
this.completeTx = this.completeTx.bind(this)
|
||||
this.reclaimTokens = this.reclaimTokens.bind(this)
|
||||
this.moveTokensFromCustomWallet = this.moveTokensFromCustomWallet.bind(this)
|
||||
}
|
||||
|
||||
// Open the wallet file, or create one if the file doesn't exist.
|
||||
@@ -148,10 +161,9 @@ class WalletAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
// Note: This method uses the system wallet, not the user or admin wallet.
|
||||
// This method returns an object that contains a private key WIF, public address,
|
||||
// and the index of the HD wallet that the key pair was generated from.
|
||||
// TODO: Allow input integer. If input is used, use that as the index. If no
|
||||
// input is provided, then call incrementNextAddress().
|
||||
async getKeyPair (hdIndex = 0) {
|
||||
try {
|
||||
if (!hdIndex) {
|
||||
@@ -160,6 +172,7 @@ class WalletAdapter {
|
||||
}
|
||||
|
||||
const mnemonic = this.bchWallet.walletInfo.mnemonic
|
||||
console.log(`getKeyPair() for HD index: ${hdIndex} called on mnemonic: ${mnemonic}`)
|
||||
|
||||
// root seed buffer
|
||||
const rootSeed = await this.bchWallet.bchjs.Mnemonic.toSeed(mnemonic)
|
||||
@@ -349,7 +362,7 @@ class WalletAdapter {
|
||||
throw err
|
||||
}
|
||||
|
||||
// return true
|
||||
// return true
|
||||
}
|
||||
|
||||
// Move tokens to an address controlled by the HD wallet, to generate a
|
||||
@@ -452,11 +465,26 @@ class WalletAdapter {
|
||||
|
||||
// Complete the partially signed transaction by signing the first input,
|
||||
// then broadcasting the transaction to the network.
|
||||
async completeTx (hex, hdIndex) {
|
||||
async completeTx (hex, hdIndex, mnemonic) {
|
||||
try {
|
||||
// Input validation
|
||||
if (!hex || typeof hex !== 'string') {
|
||||
throw new Error('hex must be a string!')
|
||||
}
|
||||
if (typeof hdIndex !== 'number' || hdIndex < 0) {
|
||||
throw new Error('hdIndex must be a non-negative number!')
|
||||
}
|
||||
if (!mnemonic || typeof mnemonic !== 'string') {
|
||||
throw new Error('mnemonic must be a string!')
|
||||
}
|
||||
// console.log('hex: ', hex)
|
||||
console.log('completeTx() hdIndex: ', hdIndex)
|
||||
console.log('completeTx() mnemonic: ', mnemonic)
|
||||
|
||||
const bchjs = this.bchWallet.bchjs
|
||||
// Instantiate the user wallet
|
||||
const userWallet = await this.instanceWallet({ mnemonic })
|
||||
|
||||
const bchjs = userWallet.bchjs
|
||||
|
||||
// instance of transaction builder
|
||||
// const transactionBuilder = new bchjs.TransactionBuilder()
|
||||
@@ -479,8 +507,10 @@ class WalletAdapter {
|
||||
// 'mainnet'
|
||||
// )
|
||||
|
||||
console.log('completeTx() userWallet.walletInfo: ', userWallet.walletInfo)
|
||||
|
||||
// Get the keypair for the address used in the Order
|
||||
const keyPair = await this.getKeyPair(hdIndex)
|
||||
const keyPair = await userWallet.getKeyPair(hdIndex)
|
||||
console.log(`maker keyPair: ${JSON.stringify(keyPair, null, 2)}`)
|
||||
const makerECPair = bchjs.ECPair.fromWIF(keyPair.wif)
|
||||
|
||||
@@ -563,6 +593,65 @@ class WalletAdapter {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// This function is consumed by the use-cases/order.js/createOrder() function.
|
||||
// Tokens are moved from the primary address to a holding address to protect the Order UTXO.
|
||||
async moveTokensFromCustomWallet (inObj = {}) {
|
||||
try {
|
||||
const { tokenId, qty, wallet } = inObj
|
||||
// Input validation
|
||||
if (!tokenId || typeof tokenId !== 'string') {
|
||||
throw new Error('tokenId must be a string!')
|
||||
}
|
||||
if (!qty) {
|
||||
throw new Error('qty must be a number!')
|
||||
}
|
||||
if (!wallet) {
|
||||
throw new Error('wallet is required!')
|
||||
}
|
||||
|
||||
// Replace the system wallet with the user wallet.
|
||||
// this.bchWallet = wallet
|
||||
|
||||
// DEV Note: Storing all token Offers in index 2 of the HD wallet.
|
||||
// This makes them easy to sweep, and can be uniquely identified by their UTXO.
|
||||
// Note: use walle.getKeyPair() to use the user wallet. this.getKeyPair uses the system wallet.
|
||||
const keyPair = await wallet.getKeyPair(2)
|
||||
console.log('keyPair: ', keyPair)
|
||||
|
||||
const receiver = {
|
||||
address: keyPair.cashAddress,
|
||||
tokenId,
|
||||
qty
|
||||
}
|
||||
console.log('receiver: ', receiver)
|
||||
|
||||
// Update the UTXO store of the wallet.
|
||||
await wallet.initialize()
|
||||
|
||||
// Get the token type of the token being moved.
|
||||
// Combine Fungible and NFT token UTXOs.
|
||||
let tokenUtxos = wallet.utxos.utxoStore.slpUtxos.type1.tokens.concat(
|
||||
wallet.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 wallet.sendTokens(receiver, 3)
|
||||
const utxoInfo = {
|
||||
txid,
|
||||
vout: 1,
|
||||
hdIndex: keyPair.hdIndex,
|
||||
tokenType: tokenUtxos[0].tokenType
|
||||
}
|
||||
|
||||
return utxoInfo
|
||||
} catch (err) {
|
||||
console.error('Error in wallet.js/moveTokensFromCustomWallet()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default WalletAdapter
|
||||
|
||||
@@ -35,6 +35,8 @@ class OrderRESTControllerLib {
|
||||
// console.log('body: ', ctx.request.body)
|
||||
|
||||
const orderObj = ctx.request.body.order
|
||||
const user = ctx.state.user
|
||||
orderObj.userId = user._id
|
||||
console.log('orderObj: ', orderObj)
|
||||
|
||||
const { eventId, noteId } = await _this.useCases.order.createOrder(orderObj)
|
||||
|
||||
@@ -7,6 +7,7 @@ import Router from 'koa-router'
|
||||
|
||||
// Local libraries.
|
||||
import OrderRESTControllerLib from './controller.js'
|
||||
import Validators from '../middleware/validators.js'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -32,12 +33,14 @@ class OrderRouter {
|
||||
}
|
||||
|
||||
// Encapsulate dependencies.
|
||||
this.validators = new Validators()
|
||||
this.orderRESTController = new OrderRESTControllerLib(dependencies)
|
||||
|
||||
// Instantiate the router and set the base route.
|
||||
const baseUrl = '/order'
|
||||
this.router = new Router({ prefix: baseUrl })
|
||||
|
||||
this.createOrder = this.createOrder.bind(this)
|
||||
_this = this
|
||||
}
|
||||
|
||||
@@ -49,7 +52,7 @@ class OrderRouter {
|
||||
}
|
||||
|
||||
// Define the routes and attach the controller.
|
||||
this.router.post('/', _this.orderRESTController.createOrder)
|
||||
this.router.post('/', this.createOrder)
|
||||
this.router.get('/list/all/:page', _this.orderRESTController.listOrders)
|
||||
this.router.post('/delete', _this.orderRESTController.deleteOrder)
|
||||
|
||||
@@ -57,6 +60,11 @@ class OrderRouter {
|
||||
app.use(_this.router.routes())
|
||||
app.use(_this.router.allowedMethods())
|
||||
}
|
||||
|
||||
async createOrder (ctx, next) {
|
||||
await _this.validators.ensureUser(ctx, next)
|
||||
await _this.orderRESTController.createOrder(ctx, next)
|
||||
}
|
||||
}
|
||||
|
||||
export default OrderRouter
|
||||
|
||||
@@ -10,6 +10,8 @@ import UserRESTControllerLib from './controller.js'
|
||||
|
||||
import Validators from '../middleware/validators.js'
|
||||
|
||||
import config from '../../../../config/index.js'
|
||||
|
||||
let _this
|
||||
|
||||
class UserRouter {
|
||||
@@ -34,6 +36,7 @@ class UserRouter {
|
||||
}
|
||||
|
||||
// Encapsulate dependencies.
|
||||
this.config = config
|
||||
this.userRESTController = new UserRESTControllerLib(dependencies)
|
||||
this.validators = new Validators()
|
||||
|
||||
@@ -52,7 +55,7 @@ class UserRouter {
|
||||
}
|
||||
|
||||
// Define the routes and attach the controller.
|
||||
this.router.post('/', this.userRESTController.createUser)
|
||||
this.router.post('/', this.createUser)
|
||||
this.router.get('/', this.getAll)
|
||||
this.router.get('/:id', this.getById)
|
||||
this.router.put('/:id', this.updateUser)
|
||||
@@ -63,13 +66,21 @@ class UserRouter {
|
||||
app.use(this.router.allowedMethods())
|
||||
}
|
||||
|
||||
async createUser (ctx, next) {
|
||||
if (process.env.DISABLE_NEW_ACCOUNTS) {
|
||||
await _this.validators.ensureAdmin(ctx, next)
|
||||
}
|
||||
await _this.userRESTController.createUser(ctx, next)
|
||||
return true
|
||||
}
|
||||
|
||||
async getAll (ctx, next) {
|
||||
await _this.validators.ensureUser(ctx, next)
|
||||
await _this.validators.ensureAdmin(ctx, next)
|
||||
await _this.userRESTController.getUsers(ctx, next)
|
||||
}
|
||||
|
||||
async getById (ctx, next) {
|
||||
await _this.validators.ensureUser(ctx, next)
|
||||
await _this.validators.ensureTargetUserOrAdmin(ctx, next)
|
||||
await _this.userRESTController.getUser(ctx, next)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/*
|
||||
This Controller library is concerned with timer-based functions that are
|
||||
kicked off periodicially.
|
||||
kicked off periodically.
|
||||
*/
|
||||
|
||||
import config from '../../config/index.js'
|
||||
@@ -60,7 +60,7 @@ class TimerControllers {
|
||||
clearInterval(this.gcOffersInt)
|
||||
// clearInterval(this.checkDupOffers)
|
||||
clearInterval(this.optimizeWalletHandle)
|
||||
clearInterval(this.cleanusageHandle)
|
||||
clearInterval(this.cleanUsageHandle)
|
||||
}
|
||||
|
||||
// Garbage Collect the Orders.
|
||||
@@ -114,6 +114,9 @@ class TimerControllers {
|
||||
// Clean the usage state so that stats reflect the last 24 hours.
|
||||
cleanUsage () {
|
||||
try {
|
||||
const now = new Date()
|
||||
console.log(`cleanUsage() Timer Controller executing at ${now.toLocaleString()}`)
|
||||
|
||||
this.useCases.usage.cleanUsage()
|
||||
|
||||
return true
|
||||
|
||||
@@ -514,7 +514,7 @@ class OfferUseCases {
|
||||
|
||||
// This function is called by the P2WDB webhook REST API handler. When a
|
||||
// Counter Offer is passed to bch-dex by the P2WDB, the data is then passed
|
||||
// to this function. It does due dilligence on the Counter Offer, then signs
|
||||
// to this function. It does due diligence on the Counter Offer, then signs
|
||||
// and broadcasts the transaction to accept the Counter Offer.
|
||||
async acceptCounterOffer (offerData) {
|
||||
try {
|
||||
@@ -535,7 +535,7 @@ class OfferUseCases {
|
||||
let orderData = {}
|
||||
try {
|
||||
orderData = await this.orderUseCase.findOrderByUtxo(offerData)
|
||||
// console.log(`orderData: ${JSON.stringify(orderData, null, 2)}`)
|
||||
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. Skipping.')
|
||||
|
||||
@@ -607,8 +607,19 @@ class OfferUseCases {
|
||||
throw new Error(`The Counter Offer has an output address of ${addrInCounterOffer}, which does not match the Maker address of ${makerAddr} in the Offer.`)
|
||||
}
|
||||
|
||||
// Get the User ID from the Order model.
|
||||
const userId = orderData.userId
|
||||
|
||||
// Get the User model from the database.
|
||||
this.UserModel = this.adapters.localdb.Users
|
||||
const user = await this.UserModel.findById(userId)
|
||||
console.log('user: ', user)
|
||||
|
||||
// Get the HD index from the Order model.
|
||||
const hdIndex = orderData.hdIndex
|
||||
|
||||
// Sign and broadcast the transaction.
|
||||
const txid = await this.adapters.wallet.completeTx(txHex, orderData.hdIndex)
|
||||
const txid = await this.adapters.wallet.completeTx(txHex, hdIndex, user.mnemonic)
|
||||
console.log('txid: ', txid)
|
||||
|
||||
return txid
|
||||
|
||||
+41
-12
@@ -25,6 +25,7 @@ class OrderLib {
|
||||
// Encapsulate dependencies
|
||||
this.orderEntity = new OrderEntity()
|
||||
this.OrderModel = this.adapters.localdb.Order
|
||||
this.UserModel = this.adapters.localdb.Users
|
||||
this.bch = this.adapters.bch
|
||||
this.config = config
|
||||
this.retryQueue = new RetryQueue({ retryPeriod: 1000, attempts: 3 })
|
||||
@@ -43,8 +44,32 @@ class OrderLib {
|
||||
|
||||
if (!entryObj.tokenId) throw new Error('entry does not contain required properties')
|
||||
|
||||
const user = await this.UserModel.findById(entryObj.userId)
|
||||
if (!user) throw new Error('user not found')
|
||||
|
||||
console.log(`Using FullStack.cash: ${this.config.useFullStackCash}`)
|
||||
const advancedConfig = {}
|
||||
if (this.config.useFullStackCash) {
|
||||
advancedConfig.interface = 'rest-api'
|
||||
advancedConfig.restURL = this.config.apiServer
|
||||
advancedConfig.apiToken = this.config.apiToken
|
||||
} else {
|
||||
advancedConfig.interface = 'consumer-api'
|
||||
advancedConfig.restURL = this.config.consumerUrl
|
||||
}
|
||||
|
||||
// Instantiate minimal-slp-wallet with the user's mnemonic.
|
||||
const BchWallet = this.adapters.wallet.BchWallet
|
||||
const userWallet = new BchWallet(user.mnemonic, advancedConfig)
|
||||
|
||||
// Wait for wallet to initialize.
|
||||
await userWallet.walletInfoPromise
|
||||
await userWallet.initialize()
|
||||
|
||||
// Specify the address to send payment.
|
||||
entryObj.makerAddr = this.adapters.wallet.bchWallet.walletInfo.cashAddress
|
||||
console.log('userWallet.walletInfo: ', userWallet.walletInfo)
|
||||
entryObj.makerAddr = userWallet.walletInfo.cashAddress
|
||||
console.log('entryObj.makerAddr: ', entryObj.makerAddr)
|
||||
// console.log('entryObj.makerAddr: ', entryObj.makerAddr)
|
||||
|
||||
// Input Validation
|
||||
@@ -53,7 +78,8 @@ class OrderLib {
|
||||
|
||||
// Optimize the wallet to speed up working with it.
|
||||
console.log('Optimizing wallet before creating new order.')
|
||||
await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.optimize, {})
|
||||
// await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.optimize, {})
|
||||
await userWallet.optimize()
|
||||
|
||||
// Ensure sufficient tokens exist to create the order.
|
||||
// await this.ensureFunds(orderEntity)
|
||||
@@ -61,24 +87,26 @@ class OrderLib {
|
||||
|
||||
// Get Ticker for token ID.
|
||||
// const tokenData = await this.adapters.wallet.bchWallet.getTxData([entryObj.tokenId])
|
||||
const tokenData = await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.getTxData, [entryObj.tokenId])
|
||||
// const tokenData = await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.getTxData, [entryObj.tokenId])
|
||||
const tokenData = await userWallet.getTxData([entryObj.tokenId])
|
||||
// console.log(`tokenData: ${JSON.stringify(tokenData, null, 2)}`)
|
||||
orderEntity.ticker = tokenData[0].tokenTicker
|
||||
|
||||
// Move the tokens to holding address.
|
||||
const moveObj = {
|
||||
tokenId: orderEntity.tokenId,
|
||||
qty: orderEntity.numTokens
|
||||
qty: orderEntity.numTokens,
|
||||
wallet: userWallet
|
||||
}
|
||||
// const utxoInfo = await this.adapters.wallet.moveTokens(moveObj)
|
||||
const utxoInfo = await this.retryQueue.addToQueue(this.adapters.wallet.moveTokens, moveObj)
|
||||
const utxoInfo = await this.retryQueue.addToQueue(this.adapters.wallet.moveTokensFromCustomWallet, moveObj)
|
||||
// console.log('utxoInfo: ', utxoInfo)
|
||||
|
||||
// Update the UTXO store for the wallet.
|
||||
await this.adapters.wallet.bchWallet.bchjs.Util.sleep(3000)
|
||||
await userWallet.bchjs.Util.sleep(3000)
|
||||
// await this.adapters.wallet.bchWallet.getUtxos()
|
||||
await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.initialize, {})
|
||||
|
||||
// await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.initialize, {})
|
||||
await userWallet.initialize()
|
||||
// Update the order with the new UTXO information.
|
||||
orderEntity.utxoTxid = utxoInfo.txid
|
||||
orderEntity.utxoVout = utxoInfo.vout
|
||||
@@ -86,6 +114,7 @@ class OrderLib {
|
||||
|
||||
// Add P2WDB specific flag for signaling that this is a new offer.
|
||||
orderEntity.dataType = 'offer'
|
||||
orderEntity.userId = user._id
|
||||
|
||||
// Post the new Order information to Nostr under the topic set in the
|
||||
// config file.
|
||||
@@ -159,7 +188,7 @@ class OrderLib {
|
||||
)
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
} else {
|
||||
// Buy Order
|
||||
throw new Error('Buy orders are not supported yet.')
|
||||
@@ -286,11 +315,11 @@ class OrderLib {
|
||||
try {
|
||||
const data = await this.OrderModel.find({})
|
||||
|
||||
// Sort entries so newest entries show first.
|
||||
// Sort entries so newest entries show first.
|
||||
.sort('-timestamp')
|
||||
// Skip to the start of the selected page.
|
||||
// Skip to the start of the selected page.
|
||||
.skip(page * DEFAULT_ENTRIES_PER_PAGE)
|
||||
// Only return 20 results.
|
||||
// Only return 20 results.
|
||||
.limit(DEFAULT_ENTRIES_PER_PAGE)
|
||||
|
||||
return data
|
||||
|
||||
@@ -36,7 +36,11 @@ class UsageUseCases {
|
||||
const now = new Date()
|
||||
const twentyFourHoursAgo = now.getTime() - (60000 * 60 * 24)
|
||||
|
||||
console.log('cleanUsage() now: ', now)
|
||||
console.log('cleanUsage() restCalls.length before filtering: ', restCalls.length)
|
||||
restCalls = restCalls.filter(x => x.timestamp > twentyFourHoursAgo)
|
||||
console.log('cleanUsage() restCalls.length after filtering: ', restCalls.length)
|
||||
|
||||
return restCalls
|
||||
} catch (err) {
|
||||
console.error('Error in usage-use-cases.js/cleanUsage()')
|
||||
|
||||
+10
-1
@@ -16,7 +16,7 @@ class UserLib {
|
||||
'Instance of adapters must be passed in when instantiating User Use Cases library.'
|
||||
)
|
||||
}
|
||||
|
||||
this.BchWallet = this.adapters.wallet.BchWallet
|
||||
// Encapsulate dependencies
|
||||
this.UserEntity = new UserEntity()
|
||||
this.UserModel = this.adapters.localdb.Users
|
||||
@@ -30,6 +30,11 @@ class UserLib {
|
||||
const userEntity = this.UserEntity.validate(userObj)
|
||||
const user = new this.UserModel(userEntity)
|
||||
|
||||
const wallet = new this.BchWallet()
|
||||
const walletInfo = await wallet.walletInfoPromise
|
||||
const mnemonic = walletInfo.mnemonic
|
||||
user.mnemonic = mnemonic
|
||||
|
||||
// Enforce default value of 'user'
|
||||
user.type = 'user'
|
||||
// console.log('user: ', user)
|
||||
@@ -102,6 +107,7 @@ class UserLib {
|
||||
|
||||
// Input Validation
|
||||
// Optional inputs, but they must be strings if included.
|
||||
|
||||
if (newData.email && typeof newData.email !== 'string') {
|
||||
throw new Error("Property 'email' must be a string!")
|
||||
}
|
||||
@@ -111,6 +117,9 @@ class UserLib {
|
||||
if (newData.password && typeof newData.password !== 'string') {
|
||||
throw new Error("Property 'password' must be a string!")
|
||||
}
|
||||
if (newData.mnemonic) {
|
||||
throw new Error("Property 'mnemonic' cannot be updated!")
|
||||
}
|
||||
|
||||
// Save a copy of the original user type.
|
||||
const userType = existingUser.type
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
/key/swarm/psk/1.0.0/
|
||||
/base16/
|
||||
bbd935b70105b03ebd0c6a3c2d2730cd22fc0d18c490ccf689b6c8a22e1bed2a
|
||||
@@ -5,16 +5,16 @@ import axios from 'axios'
|
||||
import sinon from 'sinon'
|
||||
import util from 'util'
|
||||
|
||||
import UserController from '../../../src/controllers/rest-api/users/controller.js'
|
||||
import Adapters from '../../../src/adapters/index.js'
|
||||
import UseCases from '../../../src/use-cases/index.js'
|
||||
// import UserController from '../../../src/controllers/rest-api/users/controller.js'
|
||||
// import Adapters from '../../../src/adapters/index.js'
|
||||
// import UseCases from '../../../src/use-cases/index.js'
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const LOCALHOST = `http://localhost:${config.port}`
|
||||
|
||||
const context = {}
|
||||
const adapters = new Adapters()
|
||||
let uut
|
||||
// const adapters = new Adapters()
|
||||
// let uut
|
||||
let sandbox
|
||||
|
||||
// const mockContext = require('../../unit/mocks/ctx-mock').context
|
||||
@@ -50,8 +50,8 @@ if (!config.noMongo) {
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
const useCases = new UseCases({ adapters })
|
||||
uut = new UserController({ adapters, useCases })
|
||||
// const useCases = new UseCases({ adapters })
|
||||
// uut = new UserController({ adapters, useCases })
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
@@ -173,6 +173,60 @@ if (!config.noMongo) {
|
||||
)
|
||||
assert.property(result.data, 'token', 'Token property exists.')
|
||||
assert.equal(result.data.user.type, 'user')
|
||||
assert.property(result.data.user, 'mnemonic')
|
||||
})
|
||||
it('should reject signup when DISABLE_NEW_ACCOUNTS is true', async () => {
|
||||
try {
|
||||
process.env.DISABLE_NEW_ACCOUNTS = true
|
||||
const options = {
|
||||
method: 'POST',
|
||||
url: `${LOCALHOST}/users`,
|
||||
data: {
|
||||
email: 'test2@test.com',
|
||||
password: 'supersecretpassword',
|
||||
name: 'test3'
|
||||
}
|
||||
}
|
||||
|
||||
await axios(options)
|
||||
|
||||
assert(false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
assert(err.response.status === 401, 'Error code 401 expected.')
|
||||
}
|
||||
})
|
||||
it('admin can create a user when DISABLE_NEW_ACCOUNTS is true', async () => {
|
||||
process.env.DISABLE_NEW_ACCOUNTS = true
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: `${LOCALHOST}/users`,
|
||||
headers: {
|
||||
Authorization: `Bearer ${context.adminJWT}`
|
||||
},
|
||||
data: {
|
||||
user: {
|
||||
email: 'fromAdmin@test.com',
|
||||
password: 'supersecretpassword',
|
||||
name: 'test3'
|
||||
}
|
||||
}
|
||||
}
|
||||
const result = await axios(options)
|
||||
|
||||
context.user = result.data.user
|
||||
context.token = result.data.token
|
||||
|
||||
assert(result.status === 200, 'Status Code 200 expected.')
|
||||
assert(
|
||||
result.data.user.email === 'fromAdmin@test.com',
|
||||
'Email of test expected'
|
||||
)
|
||||
assert(
|
||||
result.data.user.password === undefined,
|
||||
'Password expected to be omited'
|
||||
)
|
||||
assert.property(result.data, 'token', 'Token property exists.')
|
||||
assert.equal(result.data.user.type, 'user')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -249,52 +303,52 @@ if (!config.noMongo) {
|
||||
}
|
||||
})
|
||||
|
||||
it('should fetch all users', async () => {
|
||||
const { token } = context
|
||||
// it('should fetch all users', async () => {
|
||||
// const { token } = context
|
||||
|
||||
const options = {
|
||||
method: 'GET',
|
||||
url: `${LOCALHOST}/users`,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
}
|
||||
const result = await axios(options)
|
||||
// const options = {
|
||||
// method: 'GET',
|
||||
// url: `${LOCALHOST}/users`,
|
||||
// headers: {
|
||||
// Accept: 'application/json',
|
||||
// Authorization: `Bearer ${token}`
|
||||
// }
|
||||
// }
|
||||
// const result = await axios(options)
|
||||
|
||||
const users = result.data.users
|
||||
// console.log(`users: ${util.inspect(users)}`)
|
||||
// const users = result.data.users
|
||||
// // console.log(`users: ${util.inspect(users)}`)
|
||||
|
||||
assert.hasAnyKeys(users[0], ['type', '_id', 'email'])
|
||||
assert.isNumber(users.length)
|
||||
})
|
||||
// assert.hasAnyKeys(users[0], ['type', '_id', 'email', 'mnemonic'])
|
||||
// assert.isNumber(users.length)
|
||||
// })
|
||||
|
||||
it('should return a 422 http status if biz-logic throws an error', async () => {
|
||||
try {
|
||||
const { token } = context
|
||||
// it('should return a 422 http status if biz-logic throws an error', async () => {
|
||||
// try {
|
||||
// const { token } = context
|
||||
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.useCases.user, 'getAllUsers')
|
||||
.rejects(new Error('test error'))
|
||||
// // Force an error
|
||||
// sandbox
|
||||
// .stub(uut.useCases.user, 'getAllUsers')
|
||||
// .rejects(new Error('test error'))
|
||||
|
||||
const options = {
|
||||
method: 'GET',
|
||||
url: `${LOCALHOST}/users`,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
}
|
||||
await axios(options)
|
||||
// const options = {
|
||||
// method: 'GET',
|
||||
// url: `${LOCALHOST}/users`,
|
||||
// headers: {
|
||||
// Accept: 'application/json',
|
||||
// Authorization: `Bearer ${token}`
|
||||
// }
|
||||
// }
|
||||
// await axios(options)
|
||||
|
||||
assert.fail('Unexpected code path!')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.response.status, 422)
|
||||
assert.equal(err.response.data, 'test error')
|
||||
}
|
||||
})
|
||||
// assert.fail('Unexpected code path!')
|
||||
// } catch (err) {
|
||||
// // console.log(err)
|
||||
// assert.equal(err.response.status, 422)
|
||||
// assert.equal(err.response.data, 'test error')
|
||||
// }
|
||||
// })
|
||||
})
|
||||
|
||||
describe('GET /users/:id', () => {
|
||||
@@ -316,45 +370,45 @@ if (!config.noMongo) {
|
||||
}
|
||||
})
|
||||
|
||||
it("should throw 404 if user doesn't exist", async () => {
|
||||
const { token } = context
|
||||
// it("should throw 404 if user doesn't exist", async () => {
|
||||
// const { token } = context
|
||||
|
||||
try {
|
||||
const options = {
|
||||
method: 'GET',
|
||||
url: `${LOCALHOST}/users/5fa4bd7ee1828f5f4d8ed004`,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
}
|
||||
await axios(options)
|
||||
// try {
|
||||
// const options = {
|
||||
// method: 'GET',
|
||||
// url: `${LOCALHOST}/users/5fa4bd7ee1828f5f4d8ed004`,
|
||||
// headers: {
|
||||
// Accept: 'application/json',
|
||||
// Authorization: `Bearer ${token}`
|
||||
// }
|
||||
// }
|
||||
// await axios(options)
|
||||
|
||||
assert.equal(true, false, 'Unexpected behavior')
|
||||
} catch (err) {
|
||||
assert.equal(err.response.status, 404)
|
||||
}
|
||||
})
|
||||
// assert.equal(true, false, 'Unexpected behavior')
|
||||
// } catch (err) {
|
||||
// assert.equal(err.response.status, 404)
|
||||
// }
|
||||
// })
|
||||
|
||||
it('should throw 422 for invalid input', async () => {
|
||||
const { token } = context
|
||||
// it('should throw 422 for invalid input', async () => {
|
||||
// const { token } = context
|
||||
|
||||
try {
|
||||
const options = {
|
||||
method: 'GET',
|
||||
url: `${LOCALHOST}/users/1`,
|
||||
headers: {
|
||||
Accept: 'application/json',
|
||||
Authorization: `Bearer ${token}`
|
||||
}
|
||||
}
|
||||
await axios(options)
|
||||
// try {
|
||||
// const options = {
|
||||
// method: 'GET',
|
||||
// url: `${LOCALHOST}/users/1`,
|
||||
// headers: {
|
||||
// Accept: 'application/json',
|
||||
// Authorization: `Bearer ${token}`
|
||||
// }
|
||||
// }
|
||||
// await axios(options)
|
||||
|
||||
assert.equal(true, false, 'Unexpected behavior')
|
||||
} catch (err) {
|
||||
assert.equal(err.response.status, 422)
|
||||
}
|
||||
})
|
||||
// assert.equal(true, false, 'Unexpected behavior')
|
||||
// } catch (err) {
|
||||
// assert.equal(err.response.status, 422)
|
||||
// }
|
||||
// })
|
||||
|
||||
it('should fetch own user', async () => {
|
||||
const _id = context.user._id
|
||||
@@ -375,6 +429,7 @@ if (!config.noMongo) {
|
||||
|
||||
assert.property(user, 'type')
|
||||
assert.property(user, 'email')
|
||||
assert.property(user, 'mnemonic')
|
||||
|
||||
assert.property(user, '_id')
|
||||
assert.equal(user._id, _id)
|
||||
|
||||
@@ -62,6 +62,7 @@ describe('Admin', () => {
|
||||
describe('createSystemUser()', () => {
|
||||
it('should create admin', async () => {
|
||||
try {
|
||||
await uut.deleteExistingSystemUser()
|
||||
const result = await uut.createSystemUser()
|
||||
|
||||
assert.property(result, 'email')
|
||||
@@ -72,44 +73,58 @@ describe('Admin', () => {
|
||||
assert(false, 'Unexpected result')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle axios error', async () => {
|
||||
it('should update admin password', async () => {
|
||||
try {
|
||||
const error1 = new Error('test error')
|
||||
error1.response = {
|
||||
status: 422
|
||||
}
|
||||
const error2 = new Error('test error')
|
||||
error1.response = {
|
||||
status: 500
|
||||
}
|
||||
// The loginAdmin() function in some use cases is recursive
|
||||
// after handling the 422 error, it gets called again
|
||||
sandbox
|
||||
.stub(uut.axios, 'request')
|
||||
.onFirstCall()
|
||||
.throws(error1)
|
||||
.onSecondCall()
|
||||
.throws(error2)
|
||||
uut.config.adminPassword = 'newpassword'
|
||||
|
||||
await uut.createSystemUser()
|
||||
const fakeUser = {
|
||||
password: 'oldpassword',
|
||||
save: () => { return 'token' },
|
||||
generateToken: () => { return 'token' }
|
||||
}
|
||||
|
||||
sandbox.stub(uut.User, 'findOne').resolves(fakeUser)
|
||||
const result = await uut.createSystemUser()
|
||||
|
||||
assert.property(result, 'email')
|
||||
assert.property(result, 'password')
|
||||
assert.property(result, 'id')
|
||||
assert.property(result, 'token')
|
||||
|
||||
assert.equal(fakeUser.password, 'newpassword', 'password should be updated')
|
||||
} catch (err) {
|
||||
console.log(err)
|
||||
assert(false, 'Unexpected result')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle error', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.User, 'findOne').throws(new Error('test error'))
|
||||
await uut.createSystemUser()
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
it('should handle errors when remove user', async () => {
|
||||
describe('deleteExistingSystemUser()', () => {
|
||||
it('should delete admin', async () => {
|
||||
try {
|
||||
const error1 = new Error('test error')
|
||||
error1.response = {
|
||||
status: 422
|
||||
}
|
||||
sandbox.stub(uut.axios, 'request').throws(error1)
|
||||
sandbox.stub(uut.User, 'deleteOne').throws(new Error('test error'))
|
||||
|
||||
await uut.createSystemUser()
|
||||
sandbox.stub(uut.User, 'deleteOne').resolves(true)
|
||||
const result = await uut.deleteExistingSystemUser()
|
||||
assert.isTrue(result)
|
||||
} catch (err) {
|
||||
assert(false, 'Unexpected result')
|
||||
}
|
||||
})
|
||||
|
||||
it('should handle error when deleting admin', async () => {
|
||||
try {
|
||||
sandbox.stub(uut.User, 'deleteOne').throws(new Error('test error'))
|
||||
await uut.deleteExistingSystemUser()
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
|
||||
@@ -47,7 +47,16 @@ describe('#OrderPagination', () => {
|
||||
timestamp: 'timestamp',
|
||||
localTimestamp: 'localTimestamp',
|
||||
txid: 'txid',
|
||||
p2wdbHash: 'p2wdbHash'
|
||||
p2wdbHash: 'p2wdbHash',
|
||||
hdIndex: 0,
|
||||
nostrEventId: 'nostrEventId',
|
||||
dataType: 'dataType',
|
||||
makerAddr: 'makerAddr',
|
||||
rateInBaseUnit: 'rateInBaseUnit',
|
||||
minUnitsToExchange: 'minUnitsToExchange',
|
||||
ticker: 'ticker',
|
||||
tokenType: 1,
|
||||
userId: 'userId'
|
||||
})
|
||||
await order.save()
|
||||
})
|
||||
|
||||
@@ -31,7 +31,8 @@ describe('#User-Adapter', () => {
|
||||
testuser = new User({
|
||||
email: 'test983@test.com',
|
||||
name: 'test983',
|
||||
password: 'password'
|
||||
password: 'password',
|
||||
mnemonic: '12 words mnemonic'
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
|
||||
/*
|
||||
Unit tests for the adapter/wallet.js library.
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
|
||||
import WalletAdapter from '../../../src/adapters/wallet.js'
|
||||
import { MockBchWallet } from '../mocks/adapters/wallet.js'
|
||||
|
||||
describe('#wallet', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
|
||||
beforeEach(() => {
|
||||
uut = new WalletAdapter()
|
||||
uut.bchWallet = new MockBchWallet()
|
||||
uut.bitcoinJs = {
|
||||
Transaction: {
|
||||
fromHex: () => { return { } },
|
||||
SIGHASH_ALL: () => { return { } }
|
||||
},
|
||||
TransactionBuilder: {
|
||||
fromTransaction: () => { return { sign: () => { return { } }, build: () => { return { toHex: () => { return 'hex' } } } } }
|
||||
},
|
||||
ECPair: {
|
||||
fromWIF: () => { return { } }
|
||||
}
|
||||
}
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#moveTokensFromCustomWallet', () => {
|
||||
it('should move tokens from a custom wallet', async () => {
|
||||
sandbox.stub(uut, 'getKeyPair').resolves({
|
||||
cashAddress: 'cashAddress',
|
||||
wif: 'wif',
|
||||
hdIndex: 11
|
||||
})
|
||||
|
||||
const customWallet = new MockBchWallet()
|
||||
customWallet.getKeyPair = () => { return { cashAddress: 'cashAddress', wif: 'wif', hdIndex: 11 } }
|
||||
customWallet.sendTokens = () => { return 'move token tx id result' }
|
||||
customWallet.utxos.utxoStore.slpUtxos.type1.tokens = [
|
||||
{
|
||||
tokenId: 'tokenId',
|
||||
tokenType: 1,
|
||||
qty: 1
|
||||
}
|
||||
]
|
||||
const inObj = {
|
||||
qty: 1,
|
||||
wallet: customWallet,
|
||||
tokenId: 'tokenId'
|
||||
}
|
||||
const result = await uut.moveTokensFromCustomWallet(inObj)
|
||||
assert.equal(result.txid, 'move token tx id result')
|
||||
assert.equal(result.hdIndex, 11)
|
||||
assert.equal(result.tokenType, 1)
|
||||
assert.equal(result.vout, 1)
|
||||
})
|
||||
it('should throw an error if the wallet is not provided', async () => {
|
||||
try {
|
||||
const inObj = {
|
||||
qty: 1,
|
||||
tokenId: 'tokenId'
|
||||
}
|
||||
await uut.moveTokensFromCustomWallet(inObj)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (error) {
|
||||
assert.equal(error.message, 'wallet is required!')
|
||||
}
|
||||
})
|
||||
it('should throw an error if the tokenId is not provided', async () => {
|
||||
try {
|
||||
const inObj = {
|
||||
qty: 1,
|
||||
wallet: new MockBchWallet()
|
||||
}
|
||||
await uut.moveTokensFromCustomWallet(inObj)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (error) {
|
||||
assert.equal(error.message, 'tokenId must be a string!')
|
||||
}
|
||||
})
|
||||
it('should throw an error if the qty is not provided', async () => {
|
||||
try {
|
||||
const inObj = {
|
||||
tokenId: 'tokenId',
|
||||
wallet: new MockBchWallet()
|
||||
}
|
||||
await uut.moveTokensFromCustomWallet(inObj)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (error) {
|
||||
assert.equal(error.message, 'qty must be a number!')
|
||||
}
|
||||
})
|
||||
})
|
||||
describe('#completeTx', () => {
|
||||
// TODO: This test needs to be refactored to use the new instanceWallet() method for loading the user wallet.
|
||||
|
||||
// it('should complete a transaction', async () => {
|
||||
// sandbox.stub(uut, 'getKeyPair').resolves({
|
||||
// cashAddress: 'bitcoincash:qzl0d3gcqeypv4cy7gh8rgdszxa9vvm2acv7fqtd00',
|
||||
// wif: 'L5D2UAam8tvo3uii5kpgaGyjvVMimdrXu8nWGQSQjuuAix6ji1YQ',
|
||||
// hdIndex: 11
|
||||
// })
|
||||
// sandbox.stub(uut, 'deseralizeTx').resolves({
|
||||
// txid: 'complete tx id result'
|
||||
// })
|
||||
// sandbox.stub(uut.retryQueue, 'addToQueue').resolves('complete tx id result')
|
||||
|
||||
// const hex = 'hex'
|
||||
// const hdIndex = 11
|
||||
// const mnemonic = 'course abstract aerobic deer try switch turtle diet fence affair butter top'
|
||||
|
||||
// const txid = await uut.completeTx(hex, hdIndex, mnemonic)
|
||||
// assert.equal(txid, 'complete tx id result')
|
||||
// })
|
||||
|
||||
it('should throw an error if the hex is not provided', async () => {
|
||||
try {
|
||||
const hdIndex = 11
|
||||
await uut.completeTx(null, hdIndex)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (error) {
|
||||
assert.equal(error.message, 'hex must be a string!')
|
||||
}
|
||||
})
|
||||
it('should throw an error if the hdIndex is not provided', async () => {
|
||||
try {
|
||||
const hex = 'hex'
|
||||
await uut.completeTx(hex, null)
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (error) {
|
||||
assert.equal(error.message, 'hdIndex must be a non-negative number!')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -65,6 +65,9 @@ describe('#Order-REST-Router', () => {
|
||||
ctx.request.body = {
|
||||
order: {}
|
||||
}
|
||||
ctx.state.user = {
|
||||
id: 'testUserId'
|
||||
}
|
||||
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.useCases.order, 'createOrder').resolves({ eventId: 'testEventId', noteId: 'testNoteId' })
|
||||
@@ -80,6 +83,9 @@ describe('#Order-REST-Router', () => {
|
||||
ctx.request.body = {
|
||||
order: {}
|
||||
}
|
||||
ctx.state.user = {
|
||||
id: 'testUserId'
|
||||
}
|
||||
|
||||
// Force an error
|
||||
sandbox
|
||||
|
||||
@@ -150,7 +150,9 @@ const wallet = {
|
||||
return { cashAddress: 'fakeAddr', wif: 'fakeWif', hdIndex: 1 }
|
||||
},
|
||||
bchWallet: new MockBchWallet(),
|
||||
BchWallet: MockBchWallet,
|
||||
moveTokens: async () => {},
|
||||
moveTokensFromCustomWallet: async () => {},
|
||||
moveBch: async () => {},
|
||||
reclaimTokens: async ()=>{},
|
||||
generatePartialTx: async ()=>{},
|
||||
|
||||
@@ -173,8 +173,9 @@ describe('#order-use-case', () => {
|
||||
// Mock dependencies and force expected code path
|
||||
sandbox.stub(uut.orderEntity, 'inputValidate').returns(entryObj)
|
||||
sandbox.stub(uut, 'ensureFunds').resolves()
|
||||
sandbox.stub(uut.UserModel, 'findById').resolves({ mnemonic: 'testMnemonic' })
|
||||
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, 'moveTokensFromCustomWallet').resolves({ txid: 'fakeTxid', vout: 0, hdIndex: 1 })
|
||||
sandbox.stub(uut.adapters.wallet.bchWallet, 'initialize').resolves()
|
||||
sandbox.stub(uut.adapters.nostr, 'post').resolves('fakeEvenetId')
|
||||
|
||||
@@ -184,6 +185,55 @@ describe('#order-use-case', () => {
|
||||
assert.property(result, 'eventId')
|
||||
assert.property(result, 'noteId')
|
||||
})
|
||||
it('should create an order with consumer-api', async () => {
|
||||
uut.config.useFullStackCash = false
|
||||
const entryObj = {
|
||||
lokadId: 'SWP',
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'token-id',
|
||||
buyOrSell: 'sell',
|
||||
rateInBaseUnit: 1000,
|
||||
minUnitsToExchange: 1250,
|
||||
numTokens: 1,
|
||||
ticker: 'TEST'
|
||||
}
|
||||
|
||||
// Mock dependencies and force expected code path
|
||||
sandbox.stub(uut.orderEntity, 'inputValidate').returns(entryObj)
|
||||
sandbox.stub(uut, 'ensureFunds').resolves()
|
||||
sandbox.stub(uut.UserModel, 'findById').resolves({ mnemonic: 'testMnemonic' })
|
||||
sandbox.stub(uut.adapters.wallet.bchWallet.bchjs.Util, 'sleep').resolves()
|
||||
sandbox.stub(uut.adapters.wallet, 'moveTokensFromCustomWallet').resolves({ txid: 'fakeTxid', vout: 0, hdIndex: 1 })
|
||||
sandbox.stub(uut.adapters.wallet.bchWallet, 'initialize').resolves()
|
||||
sandbox.stub(uut.adapters.nostr, 'post').resolves('fakeEvenetId')
|
||||
|
||||
const result = await uut.createOrder(entryObj)
|
||||
console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'eventId')
|
||||
assert.property(result, 'noteId')
|
||||
})
|
||||
it('should throw error if user is not found', async () => {
|
||||
try {
|
||||
const entryObj = {
|
||||
lokadId: 'SWP',
|
||||
messageType: 1,
|
||||
messageClass: 1,
|
||||
tokenId: 'token-id',
|
||||
buyOrSell: 'sell',
|
||||
rateInBaseUnit: 1000,
|
||||
minUnitsToExchange: 1250,
|
||||
numTokens: 1,
|
||||
ticker: 'TEST'
|
||||
}
|
||||
await uut.createOrder(entryObj)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'user not found')
|
||||
}
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
/*
|
||||
Unit tests for the use-cases/usage-use-cases.js business logic library.
|
||||
|
||||
Unit tests for the use-cases/usage-use-cases.js business logic library.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
@@ -58,7 +57,7 @@ describe('#usage-use-case', () => {
|
||||
|
||||
// set older mock data
|
||||
restCalls.push({
|
||||
timestamp: now.getTime() - (60000 * 60 * 24),
|
||||
timestamp: now.getTime() - (60000 * 60 * 48), // 48 hours ago
|
||||
ip: '127.0.0.1'
|
||||
})
|
||||
|
||||
@@ -88,6 +87,7 @@ describe('#usage-use-case', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getRestSummary', () => {
|
||||
it('should get the number of rest calls', () => {
|
||||
// Set mock data
|
||||
@@ -137,6 +137,7 @@ describe('#usage-use-case', () => {
|
||||
assert.equal(result[0].ip, 'localhost')
|
||||
assert.equal(result[0].cnt, '2')
|
||||
})
|
||||
|
||||
it('should return a maximum of 20 values', () => {
|
||||
// Fill Array with 21 values
|
||||
for (let i = 0; i < 21; i++) {
|
||||
@@ -154,6 +155,7 @@ describe('#usage-use-case', () => {
|
||||
|
||||
assert.equal(result.length, 20)
|
||||
})
|
||||
|
||||
it('should handle error', () => {
|
||||
try {
|
||||
// Set mock data
|
||||
@@ -194,6 +196,7 @@ describe('#usage-use-case', () => {
|
||||
assert.equal(result[0].endpoint, 'GET /api/v1/users')
|
||||
assert.equal(result[0].cnt, '2')
|
||||
})
|
||||
|
||||
it('should return a maximum of 20 values', () => {
|
||||
// Fill Array with 21 values
|
||||
for (let i = 0; i < 21; i++) {
|
||||
|
||||
@@ -117,6 +117,29 @@ describe('#users-use-case', () => {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
it('should handle wallet errors', async () => {
|
||||
try {
|
||||
// Force an error with the database.
|
||||
class MockErrorBchWallet {
|
||||
constructor () {
|
||||
this.walletInfoPromise = Promise.reject(new Error('test error'))
|
||||
}
|
||||
}
|
||||
uut.BchWallet = MockErrorBchWallet
|
||||
|
||||
const usrObj = {
|
||||
email: 'test@test.com',
|
||||
password: 'password',
|
||||
name: 'test'
|
||||
}
|
||||
|
||||
await uut.createUser(usrObj)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should create a new user in the DB', async () => {
|
||||
// Note: The user created in this test is used by the getUser, update,
|
||||
@@ -129,7 +152,6 @@ describe('#users-use-case', () => {
|
||||
}
|
||||
|
||||
const { userData, token } = await uut.createUser(usrObj)
|
||||
|
||||
testUser = userData
|
||||
|
||||
// Commented out because there is some sophisticated mocking required that
|
||||
@@ -327,6 +349,26 @@ describe('#users-use-case', () => {
|
||||
)
|
||||
}
|
||||
})
|
||||
it('should throw an error if mnemonic is provided', async () => {
|
||||
try {
|
||||
const newData = {
|
||||
email: 'test@test.com',
|
||||
password: 'password',
|
||||
name: 'test',
|
||||
mnemonic: 'test'
|
||||
}
|
||||
|
||||
await uut.updateUser(testUser, newData)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
err.message,
|
||||
"Property 'mnemonic' cannot be updated!"
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should update the user model', async () => {
|
||||
const newData = {
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import mongoose from 'mongoose'
|
||||
import config from '../../config/index.js'
|
||||
import User from '../../src/adapters/localdb/models/users.js'
|
||||
|
||||
const EMAIL = 'test@test.com'
|
||||
const PASSWORD = 'pass'
|
||||
const EMAIL = process.env.EMAIL || 'test@test3.com'
|
||||
const PASSWORD = process.env.PASSWORD || 'pass'
|
||||
|
||||
async function addUser () {
|
||||
// Connect to the Mongo Database.
|
||||
@@ -13,8 +14,6 @@ async function addUser () {
|
||||
{ useNewUrlParser: true, useUnifiedTopology: true }
|
||||
)
|
||||
|
||||
const User = require('../../src/models/users')
|
||||
|
||||
const userData = {
|
||||
email: EMAIL,
|
||||
password: PASSWORD
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
Use the system-user-dev.json file to log in as the admin user
|
||||
and create a new user.
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
import axios from 'axios'
|
||||
import fs from 'fs'
|
||||
|
||||
// Local libraries
|
||||
// import config from '../../../config/index.js'
|
||||
// import User from '../../../src/adapters/localdb/models/users.js'
|
||||
|
||||
// Customize these variables
|
||||
const EMAIL = 'test@test.com'
|
||||
const NAME = 'tester'
|
||||
const PASSWORD = 'test'
|
||||
|
||||
async function createUser () {
|
||||
try {
|
||||
// Open the system-user-dev.json file
|
||||
const systemUser = JSON.parse(fs.readFileSync('../../../config/system-user-dev.json', 'utf8'))
|
||||
|
||||
// Log in as the admin user
|
||||
const adminLogin = {
|
||||
email: systemUser.email,
|
||||
password: systemUser.password
|
||||
}
|
||||
const response = await axios.post('http://localhost:5700/auth', adminLogin)
|
||||
console.log(`response: ${JSON.stringify(response.data, null, 2)}`)
|
||||
|
||||
const jwt = response.data.token
|
||||
|
||||
// Create a new user, using the admin credentials
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: 'http://localhost:5700/users/',
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`
|
||||
},
|
||||
data: {
|
||||
user: {
|
||||
email: EMAIL,
|
||||
name: NAME,
|
||||
password: PASSWORD
|
||||
}
|
||||
}
|
||||
}
|
||||
const result = await axios(options)
|
||||
console.log(`result: ${JSON.stringify(result.data, null, 2)}`)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
createUser()
|
||||
@@ -1,6 +1,6 @@
|
||||
import mongoose from 'mongoose'
|
||||
import config from '../../config/index.js'
|
||||
import User from '../../src/models/users.js'
|
||||
import config from '../../../config/index.js'
|
||||
import User from '../../../src/adapters/localdb/models/users.js'
|
||||
|
||||
async function getUsers () {
|
||||
// Connect to the Mongo Database.
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
Log in as an admin user to create a new user.
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
import axios from 'axios'
|
||||
// import fs from 'fs'
|
||||
|
||||
// Local libraries
|
||||
// import config from '../../../config/index.js'
|
||||
// import User from '../../../src/adapters/localdb/models/users.js'
|
||||
|
||||
// Customize these variables
|
||||
const EMAIL = 'someone@somewhere.com'
|
||||
const NAME = 'Someone'
|
||||
const PASSWORD = 'test'
|
||||
|
||||
const ADMIN_EMAIL = 'system@system.com'
|
||||
const ADMIN_PASSWORD = 'admin'
|
||||
|
||||
async function createUser () {
|
||||
try {
|
||||
// Open the system-user-prod.json file
|
||||
// const systemUser = JSON.parse(fs.readFileSync('../../../config/system-user-prod.json', 'utf8'))
|
||||
|
||||
// Log in as the admin user
|
||||
const adminLogin = {
|
||||
email: ADMIN_EMAIL,
|
||||
password: ADMIN_PASSWORD
|
||||
}
|
||||
const response = await axios.post('http://localhost:5700/auth', adminLogin)
|
||||
console.log(`response: ${JSON.stringify(response.data, null, 2)}`)
|
||||
|
||||
const jwt = response.data.token
|
||||
|
||||
// Create a new user, using the admin credentials
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: 'http://localhost:5700/users/',
|
||||
headers: {
|
||||
Authorization: `Bearer ${jwt}`
|
||||
},
|
||||
data: {
|
||||
user: {
|
||||
email: EMAIL,
|
||||
name: NAME,
|
||||
password: PASSWORD
|
||||
}
|
||||
}
|
||||
}
|
||||
const result = await axios(options)
|
||||
console.log(`result: ${JSON.stringify(result.data, null, 2)}`)
|
||||
} catch (err) {
|
||||
console.error(err)
|
||||
}
|
||||
}
|
||||
createUser()
|
||||
@@ -0,0 +1,21 @@
|
||||
import mongoose from 'mongoose'
|
||||
// import config from '../../config/index.js'
|
||||
import User from '../../../src/adapters/localdb/models/users.js'
|
||||
|
||||
const mongooseConnectStr = 'mongodb://172.17.0.1:5666/bch-swap-service-prod'
|
||||
|
||||
async function getUsers () {
|
||||
// Connect to the Mongo Database.
|
||||
mongoose.Promise = global.Promise
|
||||
mongoose.set('useCreateIndex', true) // Stop deprecation warning.
|
||||
await mongoose.connect(
|
||||
mongooseConnectStr,
|
||||
{ useNewUrlParser: true, useUnifiedTopology: true }
|
||||
)
|
||||
|
||||
const users = await User.find({}, '-password')
|
||||
console.log(`users: ${JSON.stringify(users, null, 2)}`)
|
||||
|
||||
mongoose.connection.close()
|
||||
}
|
||||
getUsers()
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
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 version will traverse the HD index of the wallet until it reaches
|
||||
the value in the nextAddress property. This ensures that all UTXOs
|
||||
that could be used by the wallet have been swept.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
import BCHJS from '@psf/bch-js'
|
||||
|
||||
import BchTokenSweep from 'bch-token-sweep'
|
||||
|
||||
// Local libraries
|
||||
import WalletAdapter from '../../src/adapters/wallet.js'
|
||||
|
||||
async function sweepFunds () {
|
||||
try {
|
||||
// Open the wallet files.
|
||||
const wallet = new WalletAdapter()
|
||||
const walletInfo = await wallet.openWallet()
|
||||
const bchWallet = await wallet.instanceWallet(walletInfo)
|
||||
console.log('walletInfo: ', walletInfo)
|
||||
|
||||
const lastIndex = walletInfo.nextAddress
|
||||
|
||||
const rootAddr = walletInfo.cashAddress
|
||||
const rootWif = walletInfo.privateKey
|
||||
console.log(`Sweeping all funds into root address ${rootAddr}...`)
|
||||
|
||||
// Generate an HD tree
|
||||
const bchjs = new BCHJS()
|
||||
const rootSeed = await bchjs.Mnemonic.toSeed(walletInfo.mnemonic)
|
||||
const masterHDNode = bchjs.HDNode.fromSeed(rootSeed)
|
||||
|
||||
let 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 HD Index ${hdIndex} with address ${cashAddress}`)
|
||||
|
||||
try {
|
||||
// Sweep tokens from address
|
||||
const sweeper = new BchTokenSweep(
|
||||
wifToSweep,
|
||||
rootWif,
|
||||
bchWallet,
|
||||
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}`)
|
||||
|
||||
// Wait between loop iterations.
|
||||
await bchjs.Util.sleep(3000)
|
||||
} catch (err) {
|
||||
console.log(`error message with index ${hdIndex}: ${err}`)
|
||||
}
|
||||
|
||||
hdIndex++
|
||||
} while (hdIndex <= lastIndex)
|
||||
|
||||
console.log(`${lastIndex} 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()
|
||||
Reference in New Issue
Block a user