Compare commits

...
51 Commits
Author SHA1 Message Date
Chris Troutner 846ee21c48 Merge pull request #63 from Permissionless-Software-Foundation/ct-unstable
feat(Orders): Added new REST endpoint to get all Orders
2022-12-01 15:12:46 -08:00
Chris Troutner 6654836440 Merge branch 'master' into ct-unstable 2022-12-01 15:10:41 -08:00
Chris Troutner a25a13673e feat(Orders): Added new REST endpoint to get all Orders 2022-12-01 15:10:04 -08:00
Chris Troutner 30eed1ffb1 Merge pull request #62 from Permissionless-Software-Foundation/ct-unstable
fix(production): Updating docker images
2022-11-29 15:49:36 -08:00
Chris Troutner 7884cffdc6 fix(production): Updating docker images 2022-11-29 15:47:18 -08:00
Chris Troutner c1a8210fa7 Merge pull request #61 from Permissionless-Software-Foundation/ct-unstable
fix(p2wdb-esm): Bumping to v2.2.8
2022-11-03 05:58:54 -07:00
Chris Troutner 9f3b482f57 fix(p2wdb-esm): Bumping to v2.2.8 2022-11-03 05:56:48 -07:00
Chris Troutner 9487027852 Merge pull request #60 from Permissionless-Software-Foundation/ct-unstable
fix(garbage collection): Fixing bug that was deleting valid offers
2022-11-02 17:32:45 -07:00
Chris Troutner 9065900608 fix(garbage collection): Fixing bug that was deleting valid offers 2022-11-02 17:02:36 -07:00
Chris Troutner 2da319f896 fix(garbage): Debugging garbage collection 2022-11-02 15:07:47 -07:00
Chris Troutner 449a98d80c fix(offers): Rounding sats 2022-11-02 15:06:10 -07:00
Chris Troutner 2343120fcf Merge pull request #59 from Permissionless-Software-Foundation/ct-unstable
Updating p2wdb lib
2022-10-10 16:49:24 -07:00
Chris Troutner 2d4fa523cd fix(production): Updating startup script 2022-10-09 08:23:26 -07:00
Chris Troutner c8cc3adbc5 fix(p2wd-esm): Bumping to v2.2.7 2022-10-09 08:16:07 -07:00
Chris Troutner 169e0ad3d2 Updating p2wdb lib 2022-10-09 06:51:03 -07:00
Chris Troutner e20a0a5d27 Rebuilding containers 2022-10-09 06:41:41 -07:00
Chris Troutner 6dda29bb14 fixing create-wallet script 2022-10-09 06:39:42 -07:00
Chris Troutner dd662e9fe9 fix(deps): Updating minimal-slp-wallet 2022-10-09 06:37:12 -07:00
Chris Troutner 9b775c5bf6 Merge pull request #58 from Permissionless-Software-Foundation/ct-unstable
Updating retry-queue to v1.0.8
2022-10-05 14:53:16 -07:00
Chris Troutner 2047673862 fix(timer controllers): Wrapper stale order/offer detection in retry-queue 2022-10-05 14:50:53 -07:00
Chris Troutner 5b545aebeb Updating RPi docker image 2022-10-03 06:46:49 -07:00
Chris Troutner 1e75816125 fix(RPi): Updating dex-ui docker image 2022-10-03 06:45:39 -07:00
Chris Troutner 455fd807a4 fix(docker): Updating dex-ui docker image 2022-10-03 05:58:33 -07:00
Chris Troutner 8b75ac0f32 Removing old comments 2022-10-02 16:50:54 -07:00
Chris Troutner f1a5ae70e2 fix(docker): Updating docker image 2022-10-02 15:43:16 -07:00
Chris Troutner 46bd532eeb Merge pull request #57 from Permissionless-Software-Foundation/ct-unstable
fixing missing ticker
2022-10-02 14:07:43 -07:00
Chris Troutner 6f99b215cd fixing missing ticker 2022-10-02 14:03:27 -07:00
Chris Troutner 3482540fc8 fix(order): fixing missing ticker 2022-10-02 13:46:19 -07:00
Chris Troutner fb999c7bd4 fix(RPi): Not specifying a P2WDB string' 2022-10-02 09:02:50 -07:00
Chris Troutner a09e5a5ba7 Merge pull request #56 from Permissionless-Software-Foundation/ct-unstable
Updating docker images
2022-10-02 08:47:10 -07:00
Chris Troutner ba14b97258 fix(RPi): Updating docker container to v2.0.6 2022-10-02 08:41:57 -07:00
Chris Troutner 79ac3729c9 fix(spam): Reducing screen spam 2022-10-02 08:36:55 -07:00
Chris Troutner bd77754686 fix(docker): Updating docker image for bch-dex 2022-10-02 08:27:09 -07:00
Chris Troutner b3b5322226 Merge pull request #55 from Permissionless-Software-Foundation/ct-unstable
fix(moveTokens): Trying to fix tx-mempool conflict error
2022-10-02 08:06:17 -07:00
Chris Troutner ec1f950ffb fix(moveTokens): Trying to fix tx-mempool conflict error 2022-10-02 08:02:40 -07:00
Chris Troutner b18bc51885 Merge pull request #54 from Permissionless-Software-Foundation/ct-unstable
Added timer controller to remove duplicate offers
2022-10-02 07:22:24 -07:00
Chris Troutner e8c35dd1f3 Merge branch 'master' into ct-unstable 2022-10-02 07:19:49 -07:00
Chris Troutner 8be0accdca Merge branch 'ct-unstable' of https://github.com/Permissionless-Software-Foundation/bch-dex into ct-unstable 2022-10-02 07:18:38 -07:00
Chris Troutner 94c56a06b7 fix(offers): Added timer controller to remove duplicate offers 2022-10-02 07:18:17 -07:00
Chris Troutner 42f64e3304 Merge branch 'ct-unstable' of https://github.com/Permissionless-Software-Foundation/bch-dex into ct-unstable 2022-10-02 00:14:24 -07:00
Chris Troutner cc9bbc3c5d Merge pull request #53 from Permissionless-Software-Foundation/ct-unstable
Refactoring Maker workflow to use retry queue
2022-10-02 00:12:43 -07:00
Chris Troutner 0c6696875b fix(offer): Refactored createOffer to use retry queue 2022-10-02 00:06:02 -07:00
Chris Troutner 37b2e426a5 fix(order): Retry-Queue used to wrap workflow 2022-10-01 23:45:49 -07:00
Chris Troutner f3713985ed fix(order): Refactored to completed some to-do items 2022-10-01 22:57:29 -07:00
Chris Troutner 4ed9994414 Merge pull request #52 from Permissionless-Software-Foundation/ct-unstable
several little tweeks
2022-10-01 22:44:58 -07:00
Chris Troutner 140707ce6b fix(RPi): Updating P2WDB image 2022-10-01 16:54:49 -07:00
Chris Troutner c289e82b0e fix(scripts): Coverting scripts to ES6 2022-10-01 15:58:36 -07:00
Chris Troutner 84210d07c4 Merge branch 'ct-unstable' of https://github.com/Permissionless-Software-Foundation/bch-dex into ct-unstable 2022-10-01 15:44:51 -07:00
Chris Troutner 6d285c77cd fix(RPi): Updating docker images 2022-10-01 15:43:49 -07:00
Chris Troutner 7cefbd71ef fix(ipfs): Adding gateway setting to config 2022-10-01 14:20:37 -07:00
Chris Troutner 85d6aa7d46 Updating package.json to reflect major release 2022-10-01 13:17:30 -07:00
36 changed files with 757 additions and 350 deletions
+4 -1
View File
@@ -113,5 +113,8 @@ export default {
? parseInt(process.env.IPFS_API_PORT)
: 5001,
chatPubSubChan: 'psf-ipfs-chat-001'
chatPubSubChan: 'psf-ipfs-chat-001',
// IPFS gateway
ipfsGateway: process.env.IPFS_GATEWAY ? process.env.IPFS_GATEWAY : 'https://p2wdb-gateway-678.fullstack.cash/ipfs/'
}
+484 -211
View File
File diff suppressed because it is too large Load Diff
+7 -5
View File
@@ -1,6 +1,6 @@
{
"name": "bch-dex",
"version": "2.0.0",
"version": "2.0.2",
"description": "A DEX on BCH for trading SLP tokens.",
"main": "index.js",
"type": "module",
@@ -28,6 +28,8 @@
},
"repository": "Permissionless-Software-Foundation/bch-dex",
"dependencies": {
"@chris.troutner/retry-queue": "1.0.8",
"@psf/bch-js": "6.5.1",
"axios": "0.27.2",
"bch-message-lib": "2.2.1",
"bcryptjs": "2.4.3",
@@ -49,15 +51,15 @@
"koa-router": "10.0.0",
"koa-static": "5.0.0",
"koa2-ratelimit": "0.9.1",
"libp2p": "^0.36.2",
"libp2p": "0.36.2",
"line-reader": "0.4.0",
"minimal-slp-wallet": "5.1.0",
"minimal-slp-wallet": "5.4.3",
"mongoose": "5.13.14",
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
"nodemailer": "6.7.5",
"p2wdb": "2.2.3",
"p2wdb-esm": "2.2.8",
"passport-local": "1.0.0",
"public-ip": "^4.0.4",
"public-ip": "4.0.4",
"winston": "3.3.3",
"winston-daily-rotate-file": "4.5.0"
},
+4 -1
View File
@@ -72,7 +72,10 @@ RUN npm run docs
#EXPOSE 5010
# Start the application.
#COPY start-production.sh start-production.sh
CMD ["./start-production.sh"]
#CMD ["npm", "start"]
# Used for debugging
#COPY dummy-app.js dummy-app.js
#CMD ["node", "dummy-app.js"]
+4
View File
@@ -0,0 +1,4 @@
setInterval(function () {
const now = new Date()
console.log(`timestamp: ${now.toLocaleString()}`)
}, 5000)
@@ -24,7 +24,7 @@ export DBURL=mongodb://172.17.0.1:5666/bch-swap-service-prod
export PORT=5700
# bch-dex specific env vars
export BCH_DEX=production
export BCH_DEX=prod
export WEBHOOKSERVICE=http://172.17.0.1:5667/webhook
export WEBHOOKTARGET=http://172.17.0.1:5700/p2wdb
export APP_ID=bch-dex-001
+2 -2
View File
@@ -73,7 +73,7 @@ services:
#build:
# context: ./bch-dex/
# dockerfile: Dockerfile
image: christroutner/bch-dex:v2.0.0
image: christroutner/bch-dex:v2.0.12
container_name: bch-dex
environment:
CONSUMER_URL: 'https://free-bch.fullstack.cash'
@@ -97,7 +97,7 @@ services:
#build:
# context: ./bch-dex-ui/
# dockerfile: Dockerfile
image: christroutner/bch-dex-ui:v1.12.0
image: christroutner/bch-dex-ui:v1.2.4
container_name: dex-ui
environment:
SERVER: 'http://192.168.2.3'
@@ -24,7 +24,7 @@ export DBURL=mongodb://172.17.0.1:5666/bch-swap-service-prod
export PORT=5700
# bch-dex specific env vars
export BCH_DEX=production
export BCH_DEX=prod
export WEBHOOKSERVICE=http://172.17.0.1:5667/webhook
export WEBHOOKTARGET=http://172.17.0.1:5700/p2wdb
export APP_ID=bch-dex-001
+3 -3
View File
@@ -43,7 +43,7 @@ services:
#build:
# context: ./p2wdb/
# dockerfile: Dockerfile
image: christroutner/p2wdb-rpi:v3.1.5
image: christroutner/p2wdb-rpi:v3.1.9
container_name: p2wdb
environment:
CONSUMER_URL: 'https://wa-usa-bch-consumer.fullstackcash.nl'
@@ -70,7 +70,7 @@ services:
#build:
# context: ./bch-dex/
# dockerfile: Dockerfile
image: christroutner/bch-dex-rpi:v1.13.0
image: christroutner/bch-dex-rpi:v2.0.8
container_name: bch-dex
environment:
#CONSUMER_URL: 'https://free-bch.fullstack.cash'
@@ -95,7 +95,7 @@ services:
#build:
# context: ./bch-dex-ui/
# dockerfile: Dockerfile
image: christroutner/bch-dex-ui-rpi:v1.2.2
image: christroutner/bch-dex-ui-rpi:v1.2.4
container_name: dex-ui
logging:
driver: 'json-file'
@@ -43,6 +43,6 @@ export IPFS_TCP_PORT=4001
export IPFS_WS_PORT=4003
# P2WDB specific env vars
export ORBITDB_NAME=/orbitdb/zdpuAqNiwLiJBfbRK7uihV2hAbNSXj78ufzv5VyQb8GuvRwDh/psf-bch-p2wdb-keyvalue-v3.0.0-0001
#export ORBITDB_NAME=/orbitdb/zdpuAqNiwLiJBfbRK7uihV2hAbNSXj78ufzv5VyQb8GuvRwDh/psf-bch-p2wdb-keyvalue-v3.0.0-0001
npm start
+1 -1
View File
@@ -6,7 +6,7 @@
the DEX app wallet in order to be acceped.
*/
const axios = require('axios')
import axios from 'axios'
const LOCALHOST = 'http://localhost:5700'
// const LOCALHOST = 'http://172.17.0.1:5700'
+6 -5
View File
@@ -3,8 +3,9 @@
*/
// Public NPM libraries
const BchWallet = require('minimal-slp-wallet/index')
const fs = require('fs').promises
import BchWallet from 'minimal-slp-wallet'
import { promises as fs } from 'fs'
async function createWallet () {
try {
@@ -15,9 +16,9 @@ async function createWallet () {
}
// Wait for the wallet to be created.
this.bchWallet = new BchWallet(undefined, advancedConfig)
await this.bchWallet.walletInfoPromise
const walletData = this.bchWallet.walletInfo
const bchWallet = new BchWallet(undefined, advancedConfig)
await bchWallet.walletInfoPromise
const walletData = bchWallet.walletInfo
walletData.nextAddress = 1
// Save the wallet file to disk.
+2 -2
View File
@@ -5,10 +5,10 @@
*/
// Global npm libraries
const BchWallet = require('minimal-slp-wallet/index')
import BchWallet from 'minimal-slp-wallet'
// Local libraries
const WalletAdapter = require('../../src/adapters/wallet')
import WalletAdapter from '../../src/adapters/wallet.js'
// Constants
const EMTPY_ADDR_CUTOFF = 10
+2 -2
View File
@@ -8,10 +8,10 @@
// Public npm libraries
// const BCHJS = require('@psf/bch-js')
const BchTokenSweep = require('bch-token-sweep/index')
import BchTokenSweep from 'bch-token-sweep/index.js'
// Local libraries
const WalletAdapter = require('../../src/adapters/wallet')
import WalletAdapter from '../../src/adapters/wallet.js'
// Constants
const EMTPY_ADDR_CUTOFF = 10
+1 -1
View File
@@ -6,7 +6,7 @@
import BCHJS from '@psf/bch-js'
import MsgLib from 'bch-message-lib'
import BchWallet from 'minimal-slp-wallet/index.js'
import BchWallet from 'minimal-slp-wallet'
class Bch {
constructor () {
+1 -1
View File
@@ -8,7 +8,7 @@
import IpfsCoord from 'ipfs-coord-esm'
// import BCHJS from '@psf/bch-js';
import SlpWallet from 'minimal-slp-wallet/index.js'
import SlpWallet from 'minimal-slp-wallet'
import publicIp from 'public-ip'
// Local libraries
+1 -1
View File
@@ -6,7 +6,7 @@ const Offer = new mongoose.Schema({
tokenId: { type: String },
utxoTxid: { type: String },
utxoVout: { type: Number },
ticker: { type: String },
ticker: { type: String, default: '' },
tokenType: { type: Number },
// Trade data
+6 -1
View File
@@ -5,10 +5,11 @@
// Public npm libraries.
import axios from 'axios'
import { Write, Read } from 'p2wdb/index.js'
import P2WDB from 'p2wdb-esm'
// Local libraries
import config from '../../config/index.js'
const { Write, Read } = P2WDB
class P2wdbAdapter {
constructor (localConfig = {}) {
@@ -27,6 +28,10 @@ class P2wdbAdapter {
// Allow the localConfig to overwrite the config file values.
this.p2wdbURL = localConfig.p2wdbURL || config.p2wdbUrl
// Bind the 'this' object to all functions
this.write = this.write.bind(this)
this.checkForSufficientFunds = this.checkForSufficientFunds.bind(this)
}
// Write some data to the P2WDB
+6 -2
View File
@@ -3,7 +3,7 @@
*/
// Public npm libraries
import BchWallet from 'minimal-slp-wallet/index.js'
import BchWallet from 'minimal-slp-wallet'
import bitcoinJs from 'bitcoincashjs-lib'
@@ -31,6 +31,9 @@ class WalletAdapter {
this.bitcoinJs = bitcoinJs
this.BchWallet = BchWallet
this.bchWallet = {} // Will be replaced when initialized.
// Bind the 'this' object
this.moveTokens = this.moveTokens.bind(this)
}
// Open the wallet file, or create one if the file doesn't exist.
@@ -85,6 +88,7 @@ class WalletAdapter {
}
const advancedConfig = {}
console.log(`Using FullStack.cash: ${this.config.useFullStackCash}`)
if (this.config.useFullStackCash) {
advancedConfig.interface = 'rest-api'
advancedConfig.restURL = this.config.apiServer
@@ -356,7 +360,7 @@ class WalletAdapter {
}
// Update the UTXO store of the wallet.
await this.bchWallet.getUtxos()
await this.bchWallet.initialize()
// Get the token type of the token being moved.
// Combine Fungible and NFT token UTXOs.
@@ -47,6 +47,21 @@ class OrderRESTControllerLib {
}
}
// curl -X GET http://localhost:5700/order/list/all/0
async listOrders (ctx) {
try {
let page = ctx.params.page
if (!page) page = 0
const offers = await _this.useCases.order.listOrders(page)
ctx.body = offers
} catch (err) {
console.log('Error in listOrders REST API handler: ', err)
_this.handleError(ctx, err)
}
}
// DRY error handler
handleError (ctx, err) {
console.log('err', err)
+1
View File
@@ -50,6 +50,7 @@ class OrderRouter {
// Define the routes and attach the controller.
this.router.post('/', _this.orderRESTController.createOrder)
this.router.get('/list/all/:page', _this.orderRESTController.listOrders)
// Attach the Controller routes to the Koa app.
app.use(_this.router.routes())
+11
View File
@@ -33,6 +33,7 @@ class TimerControllers {
startTimers () {
setInterval(this.gcOrders, 60000 * 5)
setInterval(this.gcOffers, 60000 * 5)
setInterval(this.checkDupOffers, 60000 * 4.5)
}
// Garbage Collect the Orders.
@@ -54,6 +55,16 @@ class TimerControllers {
console.log('Error in timer-controllers.js/gcOffers(): ', err)
}
}
// Remove duplicate Offers
checkDupOffers () {
try {
_this.useCases.offer.removeDuplicateOffers()
} catch (err) {
// Do not throw an error. This is a top-level function.
console.log('Error in timer-controllers.js/checkDupOffers(): ', err)
}
}
}
export default TimerControllers
+3 -3
View File
@@ -67,9 +67,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.")
}
// if (!ticker || typeof ticker !== 'string') {
// throw new Error("Property 'ticker' must be a string.")
// }
if (!tokenType || typeof tokenType !== 'number') {
throw new Error("Property 'tokenType' must be a number.")
}
+10 -8
View File
@@ -7,7 +7,10 @@
The Order tracks the hdIndex address used to hold tokens or BCH for sale.
*/
class Order {
validate (data) {
// TODO: Create a fullValidate() function that validates a fully-hydrated
// order model.
inputValidate (data) {
const {
messageType,
messageClass,
@@ -16,8 +19,7 @@ class Order {
rateInBaseUnit,
minUnitsToExchange,
numTokens,
makerAddr,
ticker
makerAddr
} = data
// Input Validation
@@ -45,9 +47,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.")
}
// if (!ticker || typeof ticker !== 'string') {
// throw new Error("Property 'ticker' must be a string.")
// }
const offerData = {
messageType,
@@ -57,8 +59,8 @@ class Order {
rateInBaseUnit,
minUnitsToExchange,
numTokens,
makerAddr,
ticker
makerAddr
// ticker
}
return offerData
+67 -32
View File
@@ -15,10 +15,10 @@
// Global npm libraries
import axios from 'axios'
import RetryQueue from '@chris.troutner/retry-queue'
// Local libraries
import OfferEntity from '../../entities/offer.js'
import config from '../../../config/index.js'
const DEFAULT_ENTRIES_PER_PAGE = 20
@@ -44,9 +44,12 @@ class OfferUseCases {
// Encapsulate dependencies
this.config = config
this.axios = axios
this.offerEntity = new OfferEntity()
this.OfferModel = this.adapters.localdb.Offer
this.retryQueue = new RetryQueue({ retryPeriod: 1000, attempts: 3 })
// Bind 'this' object to functions
this.detectNsfw = this.detectNsfw.bind(this)
}
// This method is called by the POST /offer REST API controller, which is
@@ -74,14 +77,15 @@ class OfferUseCases {
// Verify that UTXO in offer is unspent. If it is spent, then ignore the
// offer.
const txid = offerObj.data.utxoTxid
const vout = offerObj.data.utxoVout
const utxoStatus = await this.adapters.bchjs.Blockchain.getTxOut(
txid,
vout
)
const utxo = {
tx_hash: offerObj.data.utxoTxid,
tx_pos: offerObj.data.utxoVout
}
// const utxoStatus = await this.adapters.wallet.bchWallet.utxoIsValid(utxo)
const utxoStatus = await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.utxoIsValid, utxo)
console.log('utxoStatus: ', utxoStatus)
if (utxoStatus === null) return false
// if (utxoStatus === null) return false
if (!utxoStatus) return false
// A new offer gets a status of 'posted'
offerObj.data.offerStatus = 'posted'
@@ -91,18 +95,20 @@ class OfferUseCases {
// Get data about the token.
const tokenId = offerEntity.tokenId
const tokenData = await this.adapters.wallet.bchWallet.getTokenData(tokenId)
// const tokenData = await this.adapters.wallet.bchWallet.getTokenData(tokenId)
const tokenData = await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.getTokenData, tokenId)
console.log(`tokenData: ${JSON.stringify(tokenData, null, 2)}`)
// Generate a 'display category' for the token. This will allow the
// front end UI to figure out how to display the token.
const displayCategory = await this.categorizeToken(offerEntity, tokenData)
const displayCategory = this.categorizeToken(offerEntity, tokenData)
console.log('displayCategory: ', displayCategory)
offerEntity.displayCategory = displayCategory
// Detect if user set the NSFW flag.
let nsfw = false
nsfw = await this.detectNsfw(tokenData)
// nsfw = await this.detectNsfw(tokenData)
nsfw = await this.retryQueue.addToQueue(this.detectNsfw, tokenData)
offerEntity.nsfw = nsfw
// Add offer to the local database.
@@ -132,7 +138,8 @@ class OfferUseCases {
const cid = mutableCid.substring(7)
// Retrieve the mutable data from Filecoin/IPFS.
const url = `https://${cid}.ipfs.w3s.link/data.json`
// const url = `https://${cid}.ipfs.w3s.link/data.json`
const url = `${this.config.ipfsGateway}${cid}/data.json`
const result = await axios.get(url)
const mutableData = result.data
console.log(`mutableData: ${JSON.stringify(mutableData, null, 2)}`)
@@ -165,7 +172,7 @@ class OfferUseCases {
// The first three are easy to categorize. The simple-nft is a fungible token
// with a quantity of 1, decimals of 0, and no minting baton. Categorizing this
// type of token is the main reason why this function exists.
async categorizeToken (offerData, tokenData) {
categorizeToken (offerData, tokenData) {
try {
// console.log(`categorizeToken(): ${JSON.stringify(offerData, null, 2)}`)
@@ -277,18 +284,11 @@ class OfferUseCases {
}
// Verify that UTXO for sale is unspent. Abort if it's been spent.
// const txid = offerInfo.utxoTxid
// const vout = offerInfo.utxoVout
const utxo = {
tx_hash: offerInfo.utxoTxid,
tx_pos: offerInfo.utxoVout
}
const utxoStatus = await this.adapters.wallet.bchWallet.utxoIsValid(utxo)
// const utxoStatus = await this.adapters.bchjs.Blockchain.getTxOut(
// txid,
// vout
// )
console.log('utxoStatus: ', utxoStatus)
if (!utxoStatus) {
console.log(`utxo txid: ${offerInfo.utxoTxid}, vout: ${offerInfo.utxoVout}`)
@@ -306,7 +306,7 @@ class OfferUseCases {
console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
// Calculate amount of sats to generate a counter offer.
let satsToMove = offerInfo.numTokens * parseInt(offerInfo.rateInBaseUnit)
let satsToMove = Math.ceil(offerInfo.numTokens * parseInt(offerInfo.rateInBaseUnit))
if (isNaN(satsToMove)) {
throw new Error('Could not calculate the amount of BCH to generate counter offer')
}
@@ -385,7 +385,7 @@ class OfferUseCases {
// Sell Offer
// Calculate the sats needed
const satsNeeded = offerEntity.numTokens * parseInt(offerEntity.rateInBaseUnit)
const satsNeeded = Math.ceil(offerEntity.numTokens * parseInt(offerEntity.rateInBaseUnit))
if (isNaN(satsNeeded)) {
throw new Error('Could not calculate sats needed!')
}
@@ -467,7 +467,7 @@ class OfferUseCases {
console.log(`txObj: ${JSON.stringify(txObj, null, 2)}`)
// Ensure the 3rd output (vout=2) contains the required amount of BCH.
const satsToReceive = orderData.numTokens * parseInt(orderData.rateInBaseUnit)
const satsToReceive = Math.ceil(orderData.numTokens * parseInt(orderData.rateInBaseUnit))
if (isNaN(satsToReceive)) {
throw new Error('Could not calculate the amount of BCH offered in the Counter Offer')
}
@@ -497,6 +497,45 @@ class OfferUseCases {
}
}
// TODO: Write unit tests for this function, then add it to the Timer Controllers
// Looks for duplicate offers and removes the duplicate.
// Duplicates are checked for when an offer is created, and so should not
// exist. But emperical testing shows that they do. This function is called
// periodically by a timer, to clean up any duplicates that slipped through
// the cracks.
async removeDuplicateOffers () {
try {
const now = new Date()
console.log(`Starting removeDuplicateOffers() at ${now.toLocaleString()}`)
let duplicateFound = false
// Get all Offers in the database.
const offers = await this.OfferModel.find({})
// console.log('offers: ', offers)
const offerZcids = []
for (let i = 0; i < offers.length; i++) {
const thisOffer = offers[i]
if (offerZcids.includes(thisOffer.p2wdbHash)) {
await thisOffer.remove()
duplicateFound = true
continue
}
// Add the zcid to the array.
offerZcids.push(thisOffer.p2wdbHash)
}
return duplicateFound
} catch (err) {
console.error('Error in removeDuplicateOffers()')
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.
@@ -515,12 +554,6 @@ class OfferUseCases {
let utxoStatus = null
try {
// Get the status of the UTXO associate with this Offer.
// utxoStatus = await this.adapters.bchjs.Blockchain.getTxOut(
// thisOffer.utxoTxid,
// thisOffer.utxoVout
// )
// Get the status of the UTXO associate with this Offer.
const utxo = {
tx_hash: thisOffer.utxoTxid,
@@ -528,7 +561,8 @@ class OfferUseCases {
}
// console.log(`Checking this UTXO: ${JSON.stringify(utxo, null, 2)}`)
utxoStatus = await this.adapters.wallet.bchWallet.utxoIsValid(utxo)
// utxoStatus = await this.adapters.wallet.bchWallet.utxoIsValid(utxo)
utxoStatus = await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.utxoIsValid, utxo)
// console.log('utxoStatus: ', utxoStatus)
} catch (err) {
// Handle corner case of bad-data in the Offer model.
@@ -545,7 +579,8 @@ class OfferUseCases {
}
// If the Offer UTXO is spent, delete the Offer model.
if (!utxoStatus) {
if (utxoStatus === false) {
console.log('utxoStatus: ', utxoStatus)
console.log(`Spent UTXO detected. Deleting this Offer: ${JSON.stringify(thisOffer, null, 2)}`)
await thisOffer.remove()
}
+47 -18
View File
@@ -2,12 +2,16 @@
Order use-case library.
*/
// Global npm libraries
import RetryQueue from '@chris.troutner/retry-queue'
// Local libraries
import wlogger from '../adapters/wlogger.js'
import OrderEntity from '../entities/order.js'
import config from '../../config/index.js'
const DEFAULT_ENTRIES_PER_PAGE = 20
class OrderLib {
constructor (localConfig = {}) {
// console.log('User localConfig: ', localConfig)
@@ -23,6 +27,10 @@ class OrderLib {
this.OrderModel = this.adapters.localdb.Order
this.bch = this.adapters.bch
this.config = config
this.retryQueue = new RetryQueue({ retryPeriod: 1000, attempts: 3 })
// Bind subfunctions to the 'this' object.
this.ensureFunds = this.ensureFunds.bind(this)
}
// Create a new order model and add it to the Mongo database.
@@ -37,33 +45,33 @@ class OrderLib {
entryObj.makerAddr = this.adapters.wallet.bchWallet.walletInfo.cashAddress
console.log('entryObj.makerAddr: ', entryObj.makerAddr)
// Get Ticker for token ID.
// TODO: Move this below the orderEntity.validate() call.
const tokenData = await this.adapters.wallet.bchWallet.getTxData([entryObj.tokenId])
// console.log(`tokenData: ${JSON.stringify(tokenData, null, 2)}`)
entryObj.ticker = tokenData[0].tokenTicker
// Input Validation
// TODO: Remove ticker from validation.
// TODO: Rename validate() to inputValidate(). Create fullValidate() that
// validates a fully hydrated Order entity.
const orderEntity = this.orderEntity.validate(entryObj)
const orderEntity = this.orderEntity.inputValidate(entryObj)
console.log('orderEntity: ', orderEntity)
// Ensure sufficient tokens exist to create the order.
await this.ensureFunds(orderEntity)
// await this.ensureFunds(orderEntity)
await this.retryQueue.addToQueue(this.ensureFunds, orderEntity)
// 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])
// 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
}
const utxoInfo = await this.adapters.wallet.moveTokens(moveObj)
// const utxoInfo = await this.adapters.wallet.moveTokens(moveObj)
const utxoInfo = await this.retryQueue.addToQueue(this.adapters.wallet.moveTokens, moveObj)
console.log('utxoInfo: ', utxoInfo)
// Update the UTXO store for the wallet.
await this.adapters.wallet.bchWallet.bchjs.Util.sleep(3000)
await this.adapters.wallet.bchWallet.getUtxos()
// await this.adapters.wallet.bchWallet.getUtxos()
await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.initialize, {})
// Update the order with the new UTXO information.
orderEntity.utxoTxid = utxoInfo.txid
@@ -79,7 +87,8 @@ class OrderLib {
data: orderEntity,
appId: this.config.p2wdbAppId
}
const hash = await this.adapters.p2wdb.write(p2wdbObj)
// const hash = await this.adapters.p2wdb.write(p2wdbObj)
const hash = await this.retryQueue.addToQueue(this.adapters.p2wdb.write, p2wdbObj)
// console.log('hash: ', hash)
// Create a MongoDB model to hold the Order
@@ -108,6 +117,7 @@ class OrderLib {
// Update the wallet balance
await this.adapters.wallet.bchWallet.getUtxos()
// console.log(`wallet UTXOs: ${JSON.stringify(this.adapters.wallet.bchWallet.utxos.utxoStore, null, 2)}`)
// Ensure the app wallet has enough funds to write to the P2WDB.
const wif = this.adapters.wallet.bchWallet.walletInfo.privateKey
@@ -116,7 +126,7 @@ class OrderLib {
// Get UTXOs.
const utxos = this.adapters.wallet.bchWallet.utxos.utxoStore
console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
if (orderEntity.buyOrSell.includes('sell')) {
// Sell Order
@@ -209,7 +219,8 @@ class OrderLib {
tx_hash: thisOrder.utxoTxid,
tx_pos: thisOrder.utxoVout
}
utxoStatus = await this.adapters.wallet.bchWallet.utxoIsValid(utxo)
// utxoStatus = await this.adapters.wallet.bchWallet.utxoIsValid(utxo)
utxoStatus = await this.retryQueue.addToQueue(this.adapters.wallet.bchWallet.utxoIsValid, utxo)
// console.log('utxoStatus: ', utxoStatus)
} catch (err) {
// Handle corner case of bad-data in the Order model.
@@ -226,7 +237,8 @@ class OrderLib {
}
// If the Order UTXO is spent, delete the Order model.
if (!utxoStatus) {
if (utxoStatus === false) {
console.log('utxoStatus: ', utxoStatus)
console.log(`Spent UTXO detected. Deleting this Order: ${JSON.stringify(thisOrder, null, 2)}`)
await thisOrder.remove()
}
@@ -236,6 +248,23 @@ class OrderLib {
throw err
}
}
async listOrders (page = 0) {
try {
const data = await this.OrderModel.find({})
// Sort entries so newest entries show first.
.sort('-timestamp')
// Skip to the start of the selected page.
.skip(page * DEFAULT_ENTRIES_PER_PAGE)
// Only return 20 results.
.limit(DEFAULT_ENTRIES_PER_PAGE)
return data
} catch (error) {
console.error('Error in use-cases/order/listOrders()')
throw error
}
}
}
export default OrderLib
+1 -1
View File
@@ -8,7 +8,7 @@ import { assert } from 'chai'
import sinon from 'sinon'
// const BCHJS = require('@psf/bch-js')
import BchWallet from 'minimal-slp-wallet/index.js'
import BchWallet from 'minimal-slp-wallet'
// Local libraries.
import P2wdbAdapter from '../../../src/adapters/p2wdb-adapter.js'
+9 -9
View File
@@ -21,10 +21,10 @@ describe('#Order-Entity', () => {
afterEach(() => sandbox.restore())
describe('#validate', () => {
describe('#inputValidate', () => {
it('should throw an error if data is not provided', () => {
try {
uut.validate()
uut.inputValidate()
} catch (err) {
// console.log(err)
assert.include(err.message, 'Cannot destructure property')
@@ -34,7 +34,7 @@ describe('#Order-Entity', () => {
it('should throw an error if messageType is not included', () => {
try {
const data = {}
uut.validate(data)
uut.inputValidate(data)
} catch (err) {
// console.log(err)
assert.include(
@@ -47,7 +47,7 @@ describe('#Order-Entity', () => {
it('should throw an error if messageClass is not included', () => {
try {
const data = { messageType: 1 }
uut.validate(data)
uut.inputValidate(data)
} catch (err) {
// console.log(err)
assert.include(
@@ -60,7 +60,7 @@ describe('#Order-Entity', () => {
it('should throw an error if tokenId is not included', () => {
try {
const data = { messageType: 1, messageClass: 1 }
uut.validate(data)
uut.inputValidate(data)
} catch (err) {
// console.log(err)
assert.include(err.message, "Property 'tokenId' must be a string.")
@@ -70,7 +70,7 @@ describe('#Order-Entity', () => {
it('should throw an error if buyOrSell is not included', () => {
try {
const data = { messageType: 1, messageClass: 1, tokenId: 'fakeId' }
uut.validate(data)
uut.inputValidate(data)
} catch (err) {
// console.log(err)
assert.include(err.message, "Property 'buyOrSell' must be a string.")
@@ -85,7 +85,7 @@ describe('#Order-Entity', () => {
tokenId: 'fakeId',
buyOrSell: 'buy'
}
uut.validate(data)
uut.inputValidate(data)
} catch (err) {
// console.log(err)
assert.include(
@@ -104,7 +104,7 @@ describe('#Order-Entity', () => {
buyOrSell: 'buy',
rateInBaseUnit: 1000
}
uut.validate(data)
uut.inputValidate(data)
} catch (err) {
// console.log(err)
assert.include(
@@ -124,7 +124,7 @@ describe('#Order-Entity', () => {
rateInBaseUnit: 1000,
minUnitsToExchange: 350
}
uut.validate(data)
uut.inputValidate(data)
} catch (err) {
// console.log(err)
assert.include(err.message, "Property 'numTokens' must be a number.")
+2
View File
@@ -35,6 +35,8 @@ class MockBchWallet {
}]
}
this.getTokenData = async () => {}
this.initialize = async () => {}
this.utxoIsValid = async () => {}
// Environment variable is used by wallet-balance.unit.js to force an error.
if (process.env.NO_UTXO) {
+33 -13
View File
@@ -74,7 +74,7 @@ describe('#offer-use-case', () => {
}
// Mock dependencies
sandbox.stub(uut.adapters.bchjs.Blockchain, 'getTxOut').resolves(null)
sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').resolves(false)
sandbox.stub(uut.adapters.wallet.bchWallet, 'getTokenData').resolves({})
sandbox.stub(uut, 'categorizeToken').resolves('nft')
sandbox.stub(uut, 'detectNsfw').resolves(false)
@@ -111,18 +111,7 @@ describe('#offer-use-case', () => {
}
// Mock dependencies
sandbox.stub(uut.adapters.bchjs.Blockchain, 'getTxOut').resolves({
bestblock:
'000000000000000000d2060b83f90f8187b92fcccb4a42aaa19ce5a305fe0ae3',
confirmations: 0,
value: 0,
scriptPubKey: {
asm: 'OP_RETURN 5262419 1 1145980243 38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0 00000000001e8480 0000000009a7ec80',
hex: '6a04534c500001010453454e442038e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b00800000000001e8480080000000009a7ec80',
type: 'nulldata'
},
coinbase: false
})
sandbox.stub(uut.adapters.wallet.bchWallet, 'utxoIsValid').resolves(true)
sandbox.stub(uut, 'categorizeToken').resolves('fungible')
sandbox.stub(uut.adapters.wallet.bchWallet, 'getTokenData').resolves({})
sandbox.stub(uut, 'detectNsfw').resolves(false)
@@ -171,4 +160,35 @@ describe('#offer-use-case', () => {
assert.equal(result, 'fungible')
})
})
describe('#removeDuplicateOffers', () => {
it('should remove duplicate entries and return true', async () => {
// Mock dependencies and force desired code path.
sandbox.stub(uut.OfferModel, 'find').resolves([
{ p2wdbHash: 'a', remove: async () => {} },
{ p2wdbHash: 'a', remove: async () => {} },
{ p2wdbHash: 'b', remove: async () => {} }
])
// sandbox.stub(uut.OfferModel, 'remove').resolves()
const result = await uut.removeDuplicateOffers()
console.log('result: ', result)
assert.equal(result, true)
})
it('should return false if there are no duplicate entries', async () => {
// Mock dependencies and force desired code path.
sandbox.stub(uut.OfferModel, 'find').resolves([
{ p2wdbHash: 'a', remove: async () => {} },
{ p2wdbHash: 'b', remove: async () => {} }
])
// sandbox.stub(uut.OfferModel, 'remove').resolves()
const result = await uut.removeDuplicateOffers()
console.log('result: ', result)
assert.equal(result, false)
})
})
})
+5 -6
View File
@@ -103,17 +103,16 @@ describe('#order-use-case', () => {
buyOrSell: 'sell',
rateInBaseUnit: 1000,
minUnitsToExchange: 1250,
numTokens: 1
numTokens: 1,
ticker: 'TEST'
}
// 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)
// Mock dependencies and force expected code path
sandbox.stub(uut.orderEntity, 'inputValidate').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.bchWallet, 'initialize').resolves()
sandbox.stub(uut.adapters.p2wdb, 'write').resolves('fakeHash')
const result = await uut.createOrder(entryObj)
+3 -3
View File
@@ -2,15 +2,15 @@
This script has not been customized for offers yet.
*/
const mongoose = require('mongoose')
import mongoose from 'mongoose'
// Force test environment
// make sure environment variable is set before this file gets called.
// see test script in package.json.
// process.env.KOA_ENV = 'test'
const config = require('../../config')
import config from '../../config'
const User = require('../../src/models/users')
import User from '../../src/models/users'
async function deleteUsers () {
// Connect to the Mongo Database.
+3 -4
View File
@@ -2,11 +2,10 @@
Get all Offers in the database.
*/
const mongoose = require('mongoose')
import mongoose from 'mongoose'
const config = require('../../config')
const Offer = require('../../src/adapters/localdb/models/offer')
import config from '../../config/index.js'
import Offer from '../../src/adapters/localdb/models/offer.js'
async function getOffers () {
// Connect to the Mongo Database.
+3 -3
View File
@@ -1,12 +1,12 @@
const mongoose = require('mongoose')
import mongoose from 'mongoose'
// Force test environment
// make sure environment variable is set before this file gets called.
// see test script in package.json.
// process.env.KOA_ENV = 'test'
const config = require('../../config')
import config from '../../config'
const User = require('../../src/models/users')
import User from '../../src/models/users'
async function deleteUsers () {
// Connect to the Mongo Database.
+3 -5
View File
@@ -1,8 +1,6 @@
const mongoose = require('mongoose')
const config = require('../../config')
const Order = require('../../src/adapters/localdb/models/order')
import mongoose from 'mongoose'
import config from '../../config/index.js'
import Order from '../../src/adapters/localdb/models/order.js'
async function getOrder () {
// Connect to the Mongo Database.
+4 -3
View File
@@ -7,11 +7,12 @@
*/
// Public npm libraries
const BCHJS = require('@psf/bch-js')
const BchTokenSweep = require('bch-token-sweep/index')
import BCHJS from '@psf/bch-js'
import BchTokenSweep from 'bch-token-sweep/index'
// Local libraries
const WalletAdapter = require('../../src/adapters/wallet')
import WalletAdapter from '../../src/adapters/wallet'
// Constants
const EMTPY_ADDR_CUTOFF = 15