Compare commits

...
36 Commits
Author SHA1 Message Date
Chris Troutner e8104a8a12 Merge pull request #49 from Permissionless-Software-Foundation/ct-unstable
fix(production): Updating docker container
2022-09-19 19:34:35 -07:00
Chris Troutner 2c43793bf2 fix(production): Updating docker container 2022-09-19 19:28:21 -07:00
Chris Troutner b0eda392a6 Merge pull request #48 from Permissionless-Software-Foundation/ct-unstable
Better error handling for tokens without mutable data
2022-09-14 06:29:01 -07:00
Chris Troutner f81d6853fc Merge branch 'ct-unstable' of https://github.com/Permissionless-Software-Foundation/bch-dex into ct-unstable 2022-09-14 06:26:20 -07:00
Chris Troutner 02a889385d fix(offer): Better error handling for tokens without mutable data 2022-09-14 06:26:12 -07:00
Chris Troutner f17b21bee8 fixing typo 2022-09-13 15:37:57 -07:00
Chris Troutner 78b9e4fcb1 fix(rpi-docker): Updating docker images 2022-09-13 15:34:50 -07:00
Chris Troutner eb1bee1b61 fix(docker): Updating docker images 2022-09-13 15:10:03 -07:00
Chris Troutner 611c2d1631 Merge pull request #47 from Permissionless-Software-Foundation/ct-unstable
feat(nsfw): Detecting tokens marked NSFW in the mutable data
2022-09-13 14:17:12 -07:00
Chris Troutner cb9f5af62e feat(nsfw): Detecting tokens marked NSFW in the mutable data 2022-09-13 13:40:01 -07:00
Chris Troutner 2fca908c97 Merge pull request #46 from Permissionless-Software-Foundation/ct-unstable
feat(NSFW): Adding hook for handling NSFW flags from users
2022-09-13 11:53:03 -07:00
Chris Troutner 3aba1e061b feat(NSFW): Adding hook for handling NSFW flags from users 2022-09-13 11:50:14 -07:00
Chris Troutner c398942c95 temp mod to docker-compose.yml 2022-09-12 13:36:24 -07:00
Chris Troutner c2bffd8c4e Merge pull request #45 from Permissionless-Software-Foundation/ct-unstable
feat(pagination): Adding filtering and pagination
2022-09-12 10:40:32 -07:00
Chris Troutner ac97831721 feat(pagination): Adding filtering and pagination 2022-09-12 10:34:45 -07:00
Chris Troutner 02eab8686c Merge pull request #44 from Permissionless-Software-Foundation/ct-unstable
feat(categorizeToken): New field to Offer model, for UI display
2022-09-11 14:45:15 -07:00
Chris Troutner 1c1169dd93 linting 2022-09-11 14:41:56 -07:00
Chris Troutner 0896adffbb feat(categorizeToken): New field to Offer model, for UI display 2022-09-11 14:36:40 -07:00
Chris Troutner 56e0f7e8df fix(p2wdb): Updating p2wdb to v2.2.3 2022-09-11 11:55:07 -07:00
Chris Troutner 34841f53db Merge pull request #43 from Permissionless-Software-Foundation/ct-unstable
fix(offer): Fixing bugs related to making and taking offers
2022-09-11 10:54:53 -07:00
Chris Troutner c27209cceb fix(offer): Fixing bugs related to making and taking offers 2022-09-11 10:51:32 -07:00
Chris Troutner 5a0fc31c7a Merge pull request #42 from Permissionless-Software-Foundation/ct-unstable
Updating depenencies. Fixing issue with garbage collection.
2022-09-11 09:24:28 -07:00
Chris Troutner 4701f245b9 fix(garbage collection): Fixing issue with garbage collection 2022-09-11 09:21:50 -07:00
Chris Troutner daa06a57ed fix(deps): Updated dependencies and updated tests 2022-09-11 07:39:58 -07:00
Chris Troutner b3157a04ed Merge pull request #41 from Permissionless-Software-Foundation/ct-unstable
Updating docker images
2022-09-11 07:06:35 -07:00
Chris Troutner 3753db46f6 fixing merge conflict 2022-09-11 07:02:27 -07:00
Chris Troutner b924449d02 Updating docker images 2022-09-11 06:57:54 -07:00
Chris Troutner dd8d992b8a Merge pull request #40 from Permissionless-Software-Foundation/ct-unstable
fix(garbage collection): Replacing web2 with web3 calls to validate UTXO
2022-09-04 15:49:43 -07:00
Chris Troutner 4ed657ae28 fix(production): Creating new v1.9.6 docker images for amd64 and arm64 2022-09-04 15:35:50 -07:00
Chris Troutner e14633db79 Updating bch-message-lib 2022-09-04 14:38:11 -07:00
Chris Troutner 6a4e749218 fix(garbage collection): Replacing web2 with web3 calls to validate UTXO 2022-09-04 14:27:51 -07:00
Chris Troutner 7a5036e3aa Merge pull request #39 from Permissionless-Software-Foundation/ct-unstable
fix(production): Created new RPi images
2022-08-28 16:20:45 -07:00
Chris Troutner cca10452c0 fix(production): Created new RPi images 2022-08-28 16:17:33 -07:00
Chris Troutner 309ea0774d Merge pull request #38 from Permissionless-Software-Foundation/ct-unstable
New Docker images
2022-08-28 10:15:13 -07:00
Chris Troutner c2b68a0787 fix(production): Using new docker images 2022-08-28 10:12:36 -07:00
Chris Troutner 2bb4cd7cae fix(production): Updating Dockerfiles 2022-08-28 09:23:47 -07:00
20 changed files with 1636 additions and 1613 deletions
+1102 -1528
View File
File diff suppressed because it is too large Load Diff
+4 -11
View File
@@ -29,7 +29,7 @@
"dependencies": {
"@psf/bch-js": "6.4.5",
"axios": "0.27.2",
"bch-message-lib": "2.2.0",
"bch-message-lib": "2.2.1",
"bcryptjs": "2.4.3",
"bitcoincashjs-lib": "3.3.3",
"glob": "7.1.6",
@@ -52,11 +52,11 @@
"koa2-ratelimit": "0.9.1",
"libp2p": "^0.36.2",
"line-reader": "0.4.0",
"minimal-slp-wallet": "4.6.5",
"minimal-slp-wallet": "5.1.0",
"mongoose": "5.13.13",
"node-fetch": "npm:@achingbrain/node-fetch@2.6.7",
"nodemailer": "6.7.5",
"p2wdb": "1.4.0",
"p2wdb": "2.2.3",
"passport-local": "1.0.0",
"public-ip": "^4.0.4",
"winston": "3.3.3",
@@ -67,19 +67,12 @@
"bch-token-sweep": "2.1.2",
"chai": "4.3.0",
"coveralls": "2.11.4",
"eslint": "7.19.0",
"eslint-config-prettier": "7.2.0",
"eslint-config-standard": "16.0.2",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-prettier": "3.3.1",
"eslint-plugin-promise": "^4.3.1",
"eslint-plugin-standard": "4.0.0",
"husky": "4.3.8",
"mocha": "10.0.0",
"nyc": "15.1.0",
"semantic-release": "19.0.3",
"sinon": "9.2.4",
"standard": "16.0.4",
"standard": "17.0.0",
"uuid": "8.3.2"
},
"release": {
+5 -5
View File
@@ -37,7 +37,7 @@ WORKDIR /home/safeuser
RUN npm install -g npm
# npm mirror to prevent direct dependency on npm.
RUN npm set registry http://94.130.170.209:4873/
#RUN npm set registry http://94.130.170.209:4873/
# Switch to user account.
#USER safeuser
@@ -46,11 +46,11 @@ RUN npm set registry http://94.130.170.209:4873/
# Clone the rest.bitcoin.com repository
WORKDIR /home/safeuser
RUN git clone https://github.com/Permissionless-Software-Foundation/bch-dex-ui
RUN git clone https://github.com/Permissionless-Software-Foundation/bch-dex-ui-v2
# Switch to the desired branch. `master` is usually stable,
# and `stage` has the most up-to-date changes.
WORKDIR /home/safeuser/bch-dex-ui
WORKDIR /home/safeuser/bch-dex-ui-v2
# For development: switch to unstable branch
RUN git checkout ct-unstable
@@ -58,7 +58,7 @@ RUN git checkout ct-unstable
# Install dependencies
#RUN mkdir .ipfsdata
#RUN npm install -g @mapbox/node-pre-gyp
RUN npm install --force
RUN npm install
# Build the site
RUN CI=true npm run build
@@ -68,6 +68,6 @@ FROM nginx
EXPOSE 80
# Copy the files built in the first container to the new NGINX container.
COPY --from=builder /home/safeuser/bch-dex-ui/public /usr/share/nginx/html
COPY --from=builder /home/safeuser/bch-dex-ui-v2/build /usr/share/nginx/html
#USER safeuser
+3 -3
View File
@@ -46,7 +46,7 @@ services:
#build:
# context: ./p2wdb/
# dockerfile: Dockerfile
image: christroutner/p2wdb:v3.0.9
image: christroutner/p2wdb:v3.1.5
container_name: p2wdb
environment:
CONSUMER_URL: 'https://free-bch.fullstack.cash'
@@ -73,7 +73,7 @@ services:
#build:
# context: ./bch-dex/
# dockerfile: Dockerfile
image: christroutner/bch-dex:v1.9.1
image: christroutner/bch-dex:v1.13.0
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.6.5
image: christroutner/bch-dex-ui:v1.12.0
container_name: dex-ui
environment:
SERVER: 'http://192.168.2.3'
+4 -4
View File
@@ -46,11 +46,11 @@ RUN npm set registry http://94.130.170.209:4873/
# Clone the rest.bitcoin.com repository
WORKDIR /home/safeuser
RUN git clone https://github.com/Permissionless-Software-Foundation/bch-dex-ui
RUN git clone https://github.com/Permissionless-Software-Foundation/bch-dex-ui-v2
# Switch to the desired branch. `master` is usually stable,
# and `stage` has the most up-to-date changes.
WORKDIR /home/safeuser/bch-dex-ui
WORKDIR /home/safeuser/bch-dex-ui-v2
# For development: switch to unstable branch
RUN git checkout ct-unstable
@@ -58,7 +58,7 @@ RUN git checkout ct-unstable
# Install dependencies
#RUN mkdir .ipfsdata
#RUN npm install -g @mapbox/node-pre-gyp
RUN npm install --force
RUN npm install
# Build the site
RUN CI=true npm run build
@@ -68,6 +68,6 @@ FROM nginx
EXPOSE 80
# Copy the files built in the first container to the new NGINX container.
COPY --from=builder /home/safeuser/bch-dex-ui/public /usr/share/nginx/html
COPY --from=builder /home/safeuser/bch-dex-ui-v2/build /usr/share/nginx/html
#USER safeuser
+19 -22
View File
@@ -14,43 +14,39 @@ services:
restart: always
ipfs:
#image: ipfs/go-ipfs:v0.13.0
image: odanado/go-ipfs:v0.11.0
image: christroutner/trickle-ipfs-rpi:v1.0.1
#image: odanado/go-ipfs:v0.11.0
container_name: ipfs
environment:
MY_ENV_VAR: 'placeholder'
IPFS_DAEMON_ARGUMENTS: '--enable-pubsub-experiment --migrate=true --agent-version-suffix=docker --routing=dhtclient'
UPLOAD_KBPS: '25'
DOWNLOAD_KBPS: '25'
logging:
driver: 'json-file'
options:
max-size: '10m'
max-file: '10'
mem_limit: 2000mb
mem_limit: 1000mb
ports:
- 4001:4001
- 4003:4003
- 172.17.0.1:5001:5001
- 172.17.0.1:8080:8080
volumes:
- ../data/go-ipfs/data:/data/ipfs
# - ../../swarm.key:/data/ipfs/swarm.key
# https://docs.docker.com/compose/compose-file/compose-file-v3/#command
# https://github.com/ipfs/go-ipfs/blob/91c52657166bcf86f2476926e4fe56694dc26562/Dockerfile#L115
command:
[
'daemon',
'--migrate=true',
'--agent-version-suffix=docker',
'--enable-pubsub-experiment'
]
- ./data/go-ipfs:/root/.ipfs
command: [
'./start-ipfs.sh'
]
restart: always
p2wdb:
#build:
# context: ./p2wdb/
# dockerfile: Dockerfile
image: christroutner/p2wdb-rpi-v3.0.6
image: christroutner/p2wdb-rpi:v3.1.5
container_name: p2wdb
environment:
CONSUMER_URL: 'https://free-bch.fullstack.cash'
CONSUMER_URL: 'https://wa-usa-bch-consumer.fullstackcash.nl'
DEBUG_LEVEL: 1
logging:
driver: 'json-file'
@@ -72,12 +68,13 @@ services:
bch-dex:
#build:
# context: ./bch-dex/
# dockerfile: Dockerfile
image: christroutner/bch-dex-rpi:v1.8.2
# context: ./bch-dex/
# dockerfile: Dockerfile
image: christroutner/bch-dex-rpi:v1.13.0
container_name: bch-dex
environment:
CONSUMER_URL: 'https://free-bch.fullstack.cash'
#CONSUMER_URL: 'https://free-bch.fullstack.cash'
CONSUMER_URL: 'https://wa-usa-bch-consumer.fullstackcash.nl'
logging:
driver: 'json-file'
options:
@@ -98,7 +95,7 @@ services:
#build:
# context: ./bch-dex-ui/
# dockerfile: Dockerfile
image: christroutner/bch-dex-ui-rpi:v1.7.4
image: christroutner/bch-dex-ui-rpi:v1.13.0
container_name: dex-ui
logging:
driver: 'json-file'
+4
View File
@@ -64,6 +64,8 @@ async function sweepNfts () {
if (thisToken.tokenType === 65) {
console.log(`NFT ${thisToken.ticker} (${thisToken.name}) found. Sweeping to ${rootAddr}`)
await walletRoot.initialize()
// Send BCH to the child address, to pay for transaction fees.
const receiver1 = [{
address: cashAddress,
@@ -76,6 +78,8 @@ async function sweepNfts () {
console.log('Waiting for indexers to update...')
await bchjs.Util.sleep(120000)
await walletChild.initialize()
// Send NFT to root address.
const receiver2 = [{
address: rootAddr,
+5
View File
@@ -37,6 +37,7 @@ class Adapters {
this.bch = new BCHAdapter()
this.config = config
this.wallet = new WalletAdapter()
localConfig.bchWallet = this.wallet.bchWallet
this.p2wdb = new P2wdbAdapter(localConfig)
this.webhook = new Webhook()
@@ -70,6 +71,10 @@ class Adapters {
// Wait until a webhook is established with the P2WDB
await this.webhook.waitUntilSuccess(this.config.webhookTarget)
// Overwrite instances of wallet used by P2WDB lib.
this.p2wdb.bchWallet = this.wallet.bchWallet
this.p2wdb.bchjs = this.wallet.bchWallet.bchjs
}
console.log('Async Adapters have been started.')
+3
View File
@@ -27,6 +27,9 @@ const Offer = new mongoose.Schema({
timestamp: { type: Number },
globalTimestamp: { type: String },
localTimestamp: { type: String },
displayCategory: { type: String },
nsfw: { type: Boolean, default: false },
flags: { type: Array },
// SWaP Protocol Properties
lokadId: { type: String },
+12 -5
View File
@@ -12,9 +12,9 @@ const config = require('../../config')
class P2wdbAdapter {
constructor (localConfig = {}) {
// Dependency injection
this.bchjs = localConfig.bchjs
if (!this.bchjs) {
throw new Error('Must pass an instance of bch-js when instantiating p2wdb.js adapter.')
this.bchWallet = localConfig.bchWallet
if (!this.bchWallet) {
throw new Error('Must pass an instance of minimal-slp-wallet as bchWallet when instantiating p2wdb.js adapter.')
}
// Encapsulate dependencies
@@ -22,6 +22,7 @@ class P2wdbAdapter {
this.config = config
this.Write = Write
this.Read = Read
this.bchjs = this.bchWallet.bchjs
// Allow the localConfig to overwrite the config file values.
this.p2wdbURL = localConfig.p2wdbURL || config.p2wdbUrl
@@ -51,6 +52,10 @@ class P2wdbAdapter {
// settings.
instantiateWriteLib (wif) {
try {
if (!wif) {
throw new Error('WIF input required when calling instantiateWriteLib()')
}
// Object used to configure the Write library.
let configObj = {}
@@ -62,7 +67,8 @@ class P2wdbAdapter {
wif,
serverURL: this.p2wdbURL,
restURL: this.bchjs.restURL,
apiToken: this.bchjs.apiToken
apiToken: this.bchjs.apiToken,
bchWallet: this.bchWallet
}
} else {
// Use web 3 infrastructure.
@@ -72,7 +78,8 @@ class P2wdbAdapter {
wif,
serverURL: this.p2wdbURL,
interface: 'consumer-api',
restURL: this.config.consumerUrl
restURL: this.config.consumerUrl,
bchWallet: this.bchWallet
}
}
+3 -1
View File
@@ -20,9 +20,10 @@ class WalletAdapter {
// Encapsulate dependencies
this.jsonFiles = new JsonFiles()
this.WALLET_FILE = WALLET_FILE
this.BchWallet = BchWallet
this.config = config
this.bitcoinJs = bitcoinJs
this.BchWallet = BchWallet
this.bchWallet = {} // Will be replaced when initialized.
}
// Open the wallet file, or create one if the file doesn't exist.
@@ -91,6 +92,7 @@ class WalletAdapter {
// Wait for wallet to initialize.
await this.bchWallet.walletInfoPromise
await this.bchWallet.initialize()
console.log(`BCH wallet initialized. Wallet address: ${this.bchWallet.walletInfo.cashAddress}`)
// console.log(`this.bchWallet.walletInfo: ${JSON.stringify(this.bchWallet.walletInfo, null, 2)}`)
+36 -3
View File
@@ -49,14 +49,47 @@ class OfferRESTControllerLib {
}
}
// curl -X GET http://localhost:5700/offer/list
// curl -X GET http://localhost:5700/offer/list/all/0
async listOffers (ctx) {
try {
const offers = await _this.useCases.offer.listOffers()
let page = ctx.params.page
if (!page) page = 0
const offers = await _this.useCases.offer.listOffers(page)
ctx.body = offers
} catch (err) {
console.log('Error in listOffers REST API handler.')
console.log('Error in listOffers REST API handler: ', err)
_this.handleError(ctx, err)
}
}
// curl -X GET http://localhost:5700/offer/list/nft/0
async listNftOffers (ctx) {
try {
let page = ctx.params.page
if (!page) page = 0
const offers = await _this.useCases.offer.listNftOffers(page)
ctx.body = offers
} catch (err) {
console.log('Error in listNftOffers REST API handler: ', err)
_this.handleError(ctx, err)
}
}
// curl -X GET http://localhost:5700/offer/list/fungible/0
async listFungibleOffers (ctx) {
try {
let page = ctx.params.page
if (!page) page = 0
const offers = await _this.useCases.offer.listFungibleOffers(page)
ctx.body = offers
} catch (err) {
console.log('Error in Fungible REST API handler: ', err)
_this.handleError(ctx, err)
}
}
+3 -1
View File
@@ -50,8 +50,10 @@ class OfferRouter {
// Define the routes and attach the controller.
this.router.post('/', _this.offerRESTController.createOffer)
this.router.get('/list', _this.offerRESTController.listOffers)
this.router.post('/take', _this.offerRESTController.takeOffer)
this.router.get('/list/all/:page', _this.offerRESTController.listOffers)
this.router.get('/list/nft/:page', _this.offerRESTController.listNftOffers)
this.router.get('/list/fungible/:page', _this.offerRESTController.listFungibleOffers)
// Attach the Controller routes to the Koa app.
app.use(_this.router.routes())
@@ -45,12 +45,23 @@ class P2WDBRESTControllerLib {
const counterOffer = ctx.request.body
await _this.useCases.offer.acceptCounterOffer(counterOffer)
//
} else if (dataType.includes('offer')) {
// Detect and handle new Offers
console.log('offer data detected')
const offerObj = ctx.request.body
await _this.useCases.offer.createOffer(offerObj)
//
} else if (dataType.includes('flag')) {
// Detect and handle data generated by users flagging NSFW Offers.
const flagData = ctx.request.body
console.log('Flag type data received: ', flagData)
// const p2wdbHash = flagData.p2wdbHash
await _this.useCases.offer.flagOffer(flagData)
} else {
console.log('Could not route P2WDB webhook data.')
}
+216 -16
View File
@@ -13,10 +13,17 @@
a partially signed transaction.
*/
// Global npm libraries
const axios = require('axios')
// Local libraries
const OfferEntity = require('../../entities/offer')
const config = require('../../../config')
const DEFAULT_ENTRIES_PER_PAGE = 20
const NFT_ENTRIES_PER_PAGE = 6
const FUNGIBLE_ENTRIES_PER_PAGE = 20
class OfferUseCases {
constructor (localConfig = {}) {
// console.log('User localConfig: ', localConfig)
@@ -35,6 +42,7 @@ class OfferUseCases {
// Encapsulate dependencies
this.config = config
this.axios = axios
this.offerEntity = new OfferEntity()
this.OfferModel = this.adapters.localdb.Offer
@@ -80,6 +88,22 @@ class OfferUseCases {
const offerEntity = this.offerEntity.validate(offerObj)
console.log('offerEntity: ', offerEntity)
// Get data about the token.
const tokenId = offerEntity.tokenId
const tokenData = await 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)
console.log('displayCategory: ', displayCategory)
offerEntity.displayCategory = displayCategory
// Detect if user set the NSFW flag.
let nsfw = false
nsfw = await this.detectNsfw(tokenData)
offerEntity.nsfw = nsfw
// Add offer to the local database.
const offerModel = new this.OfferModel(offerEntity)
await offerModel.save()
@@ -91,15 +115,145 @@ class OfferUseCases {
}
}
async listOffers () {
// By default this function returns false, to indicate the NFT is safe for work.
// If the user who created the token sets the nsfw property in the mutable data,
// this function will return true.
async detectNsfw (tokenData) {
try {
return this.OfferModel.find({}).sort('-timestamp')
let nsfw = false
const mutableCid = tokenData.mutableData
// If there is no mutable IPFS CID, then skip this token.
if (!mutableCid.includes('ipfs://')) return nsfw
// Revove the ipfs:// prefix.
const cid = mutableCid.substring(7)
// Retrieve the mutable data from Filecoin/IPFS.
const url = `https://${cid}.ipfs.w3s.link/data.json`
const result = await axios.get(url)
const mutableData = result.data
console.log(`mutableData: ${JSON.stringify(mutableData, null, 2)}`)
// Logical tests
const hasNsfw = !!mutableData.nsfw
const nsfwSetTrue = mutableData.nsfw === true
const nsfwStringTrue = mutableData.nsfw === 'true'
const nsfwDetected = hasNsfw && (nsfwSetTrue || nsfwStringTrue)
if (nsfwDetected) {
console.log('NSFW flag set as true')
nsfw = true
}
return nsfw
} catch (err) {
console.error('Error in detectNsfw(): ', err)
return false
}
}
// Categorize the token for display purposes. This will categorize a token
// into one of these categories:
// - nft
// - group
// - fungible
// - simple-nft
//
// The first three are easy to categorize. The simple-nft is a fungible token
// with a quantity of 1, decimals of 0, and no minting baton. Categorizing this
// type of token is the main reason why this function exists.
async categorizeToken (offerData, tokenData) {
try {
// console.log(`categorizeToken(): ${JSON.stringify(offerData, null, 2)}`)
// const tokenId = offerData.tokenId
//
// const tokenData = await this.adapters.wallet.bchWallet.getTokenData(tokenId)
// console.log(`tokenData: ${JSON.stringify(tokenData, null, 2)}`)
if (tokenData.genesisData.type === 65) {
return 'nft'
}
// Create a set of checks to detect a simple NFT
const isType1 = tokenData.genesisData.type === 1
const hasNoMintingBaton = !tokenData.genesisData.mintBatonIsActive
const hasNoDecimals = !tokenData.genesisData.decimals
const hasQtyOfOne = parseInt(tokenData.genesisData.tokensInCirculationStr) === 1
if (isType1 && hasNoMintingBaton && hasNoDecimals && hasQtyOfOne) {
return 'simple-nft'
}
if (isType1) return 'fungible'
throw new Error(`Unknown token type: ${tokenData.genesisData.type}`)
} catch (err) {
console.error('Error in use-cases/offer/index.js categorizeToken(): ', err)
throw err
}
}
async listOffers (page = 0) {
try {
const data = await this.OfferModel.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/offer/listOffers()')
throw error
}
}
async listNftOffers (page = 0, nsfw = false) {
try {
const data = await this.OfferModel.find({
displayCategory: { $ne: 'fungible' },
nsfw
})
// Sort entries so newest entries show first.
.sort('-timestamp')
// Skip to the start of the selected page.
.skip(page * NFT_ENTRIES_PER_PAGE)
// Only return 20 results.
.limit(NFT_ENTRIES_PER_PAGE)
// console.log('listNftOffers() returning this data: ', data)
return data
} catch (error) {
console.error('Error in use-cases/offer/listNftOffers()')
throw error
}
}
async listFungibleOffers (page = 0) {
try {
const data = await this.OfferModel.find({ displayCategory: 'fungible' })
// Sort entries so newest entries show first.
.sort('-timestamp')
// Skip to the start of the selected page.
.skip(page * FUNGIBLE_ENTRIES_PER_PAGE)
// Only return 20 results.
.limit(FUNGIBLE_ENTRIES_PER_PAGE)
// console.log('listFungibleOffers() returning this data: ', data)
return data
} catch (error) {
console.error('Error in use-cases/offer/listFungibleOffers()')
throw error
}
}
// Generate phase 2 of 3 - take the other side of an Offer.
// Based on this example:
// https://github.com/Permission=less-Software-Foundation/bch-js-examples/blob/master/bch/applications/collaborate/sell-slp/e2e-exchange/step2-purchase-tx.js
@@ -122,15 +276,21 @@ class OfferUseCases {
}
// Verify that UTXO for sale is unspent. Abort if it's been spent.
const txid = offerInfo.utxoTxid
const vout = offerInfo.utxoVout
const utxoStatus = await this.adapters.bchjs.Blockchain.getTxOut(
txid,
vout
)
// 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 === null) {
console.log(`utxo txid: ${txid}, vout: ${vout}`)
if (!utxoStatus) {
console.log(`utxo txid: ${offerInfo.utxoTxid}, vout: ${offerInfo.utxoVout}`)
// TODO: Mark this Offer as 'dead'
@@ -212,6 +372,9 @@ class OfferUseCases {
await this.adapters.wallet.bchWallet.walletInfoPromise
// console.log(`utxos: ${JSON.stringify(this.adapters.wallet.bchWallet.utxos.utxoStore, null, 2)}`)
// Refresh the wallet UTXOs
await this.adapters.wallet.bchWallet.initialize()
// Ensure the app wallet has enough funds to write to the P2WDB.
const wif = this.adapters.wallet.bchWallet.walletInfo.privateKey
const canWriteToP2WDB = await this.adapters.p2wdb.checkForSufficientFunds(wif)
@@ -352,10 +515,19 @@ 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
)
// utxoStatus = await this.adapters.bchjs.Blockchain.getTxOut(
// thisOffer.utxoTxid,
// thisOffer.utxoVout
// )
// Get the status of the UTXO associate with this Offer.
const utxo = {
tx_hash: thisOffer.utxoTxid,
tx_pos: thisOffer.utxoVout
}
// console.log(`Checking this UTXO: ${JSON.stringify(utxo, null, 2)}`)
utxoStatus = await this.adapters.wallet.bchWallet.utxoIsValid(utxo)
// console.log('utxoStatus: ', utxoStatus)
} catch (err) {
// Handle corner case of bad-data in the Offer model.
@@ -364,7 +536,7 @@ class OfferUseCases {
await thisOffer.remove()
continue
} else if (err.isAxiosError) {
console.log('Error trying to contact api.fullstack.cash')
console.log('Error trying to contact wallet service: ', err)
continue
} else {
throw err
@@ -372,7 +544,7 @@ class OfferUseCases {
}
// If the Offer UTXO is spent, delete the Offer model.
if (utxoStatus === null) {
if (!utxoStatus) {
console.log(`Spent UTXO detected. Deleting this Offer: ${JSON.stringify(thisOffer, null, 2)}`)
await thisOffer.remove()
}
@@ -391,6 +563,34 @@ class OfferUseCases {
throw err
}
}
async flagOffer (flagData) {
console.log(`flagData: ${JSON.stringify(flagData, null, 2)}`)
const p2wdbHash = flagData.data.p2wdbHash
// Get the offer from the database.
const offer = await this.findOfferByHash(p2wdbHash)
console.log(`Flagging this offer: ${JSON.stringify(offer, null, 2)}`)
if (!offer) {
throw new Error(`Offer ${p2wdbHash} not found in the database.`)
}
// Add the raw flag data to the database model.
offer.flags.push(flagData)
// If flag count is 3 or more, mark the Offer as NSFW
const flagCnt = offer.flags.length
if (flagCnt >= 3) {
offer.nsfw = true
}
// Save the updated offer data to the database.
await offer.save()
return true
}
}
module.exports = OfferUseCases
+13 -6
View File
@@ -198,10 +198,17 @@ class OrderLib {
let utxoStatus = null
try {
// Get the status of the UTXO associate with this Order.
utxoStatus = await this.adapters.bchjs.Blockchain.getTxOut(
thisOrder.utxoTxid,
thisOrder.utxoVout
)
// utxoStatus = await this.adapters.bchjs.Blockchain.getTxOut(
// thisOrder.utxoTxid,
// thisOrder.utxoVout
// )
// Get the status of the UTXO associate with this Order.
const utxo = {
tx_hash: thisOrder.utxoTxid,
tx_pos: thisOrder.utxoVout
}
utxoStatus = await this.adapters.wallet.bchWallet.utxoIsValid(utxo)
// console.log('utxoStatus: ', utxoStatus)
} catch (err) {
// Handle corner case of bad-data in the Order model.
@@ -210,7 +217,7 @@ class OrderLib {
await thisOrder.remove()
continue
} else if (err.isAxiosError) {
console.log('Error trying to contact api.fullstack.cash')
console.log('Error trying to contact wallet service: ', err)
continue
} else {
throw err
@@ -218,7 +225,7 @@ class OrderLib {
}
// If the Order UTXO is spent, delete the Order model.
if (utxoStatus === null) {
if (!utxoStatus) {
console.log(`Spent UTXO detected. Deleting this Order: ${JSON.stringify(thisOrder, null, 2)}`)
await thisOrder.remove()
}
+11 -8
View File
@@ -5,31 +5,34 @@
// Public npm libraries.
const assert = require('chai').assert
const sinon = require('sinon')
const BCHJS = require('@psf/bch-js')
// const BCHJS = require('@psf/bch-js')
const BchWallet = require('minimal-slp-wallet/index.js')
// Local libraries.
const P2wdbAdapter = require('../../../src/adapters/p2wdb-adapter')
describe('#P2wdbAdapter', () => {
let uut, sandbox
let uut, sandbox, bchWallet
beforeEach(() => {
const bchjs = new BCHJS()
beforeEach(async () => {
// const bchjs = new BCHJS()
bchWallet = new BchWallet()
await bchWallet.walletInfoPromise
uut = new P2wdbAdapter({ bchjs })
uut = new P2wdbAdapter({ bchWallet })
sandbox = sinon.createSandbox()
})
afterEach(() => sandbox.restore())
describe('#constructor', () => {
it('should throw error if bch-js is not passed in', () => {
it('should throw error if bchWallet is not passed in', () => {
try {
uut = new P2wdbAdapter()
assert.fail('Unexpected coded path')
} catch (err) {
assert.include(err.message, 'Must pass an instance of bch-js when instantiating p2wdb.js adapter.')
assert.include(err.message, 'Must pass an instance of minimal-slp-wallet as bchWallet when instantiating p2wdb.js adapter.')
}
})
})
@@ -82,7 +85,7 @@ describe('#P2wdbAdapter', () => {
assert.fail('Unexpected code path')
} catch (err) {
// console.log(err)
assert.include(err.message, 'WIF private key required')
assert.include(err.message, 'WIF input required when calling instantiateWriteLib()')
}
})
})
+1
View File
@@ -34,6 +34,7 @@ class MockBchWallet {
tokenTicker: 'TROUT'
}]
}
this.getTokenData = async () => {}
// Environment variable is used by wallet-balance.unit.js to force an error.
if (process.env.NO_UTXO) {
@@ -0,0 +1,136 @@
/*
Mocking data for Offer Use Case unit tests.
*/
const nftOffer01 = {
"messageType": 1,
"messageClass": 1,
"tokenId": "eb93f05553ff088bffb0ec687519e83c59e5108c160f7c25a4b6c45109d7e40b",
"buyOrSell": "sell",
"rateInBaseUnit": 7672536,
"minUnitsToExchange": 7672536,
"numTokens": 1,
"utxoTxid": "c736d73e274df20e9b069b4990d1a264fb80aa98d67cc6c7a39e42bff48e7c04",
"utxoVout": 1,
"timestamp": 1662930309998,
"globaltimestamp": "2022-09-11T21:05:09.998Z",
"localTimestamp": "9/11/2022, 9:05:09 PM",
"txid": "4266862b8358664996038d8c29d49dc0f3d3058fd1ef3d567b084e6b16ceb5b2",
"p2wdbHash": "zdpuArq7rCuVCGPyTnWpLjy9AYsh8yrbwjGdKwg2GDDALkM8t",
"offerStatus": "posted",
"makerAddr": "bitcoincash:qrqlz63cwmu0hcmsrfnd8jemn3atkpaqds6tf4ksrr",
"ticker": "TV001",
"tokenType": 65
}
const nftTokenData01 = {
"genesisData": {
"type": 65,
"ticker": "TV001",
"name": "Introduction to NFTs on BCH",
"tokenId": "eb93f05553ff088bffb0ec687519e83c59e5108c160f7c25a4b6c45109d7e40b",
"documentUri": "ipfs://bafybeibmgilu4lk3uivwyzhtm7jorl5zbsluewqmcsxs6otzrphh33okk4",
"documentHash": "c1731268f4873f1928438abdaf6ffc546d86a1817dc6f3c6bc73fbdfb4664f10",
"decimals": 0,
"mintBatonIsActive": false,
"tokensInCirculationBN": "1",
"tokensInCirculationStr": "1",
"blockCreated": 740395,
"totalBurned": "0",
"totalMinted": "1",
"parentGroupId": "030563ddd65772d8e9b79b825529ed53c7d27037507b57c528788612b4911107"
},
"immutableData": "ipfs://bafybeibmgilu4lk3uivwyzhtm7jorl5zbsluewqmcsxs6otzrphh33okk4",
"mutableData": "ipfs://bafybeifzhunfpodsztsj5x4ypopngkxuapbxwaxaxkahaybdktk6joqtlq"
}
const simpleNftOffer01 = {
"messageType": 1,
"messageClass": 1,
"tokenId": "a80c89d2f9f7ad7584743587c4950a0f28514ba8054e8cec4e871747cf9c162c",
"buyOrSell": "sell",
"rateInBaseUnit": 76725,
"minUnitsToExchange": 76725,
"numTokens": 1,
"utxoTxid": "dd51a8bf96c8ab16a2935b09e51650e1769a1fa75cce9f0455abdbf72071d3c2",
"utxoVout": 1,
"timestamp": 1662930102814,
"globaltimestamp": "2022-09-11T21:01:42.814Z",
"localTimestamp": "9/11/2022, 9:01:42 PM",
"txid": "4013177602ee75bbc5c4a67ea8d6d80e4f3d12b9c46ae2a6643911f48257b578",
"p2wdbHash": "zdpuAzyLPFBwFWbNi8XQuxgAx9aBNGWZEGqib7KE6Hp3AEEhq",
"offerStatus": "posted",
"makerAddr": "bitcoincash:qrqlz63cwmu0hcmsrfnd8jemn3atkpaqds6tf4ksrr",
"ticker": "BAA",
"tokenType": 1
}
const simpleNftTokenData01 = {
"genesisData": {
"type": 1,
"ticker": "BAA",
"name": "Bitcoin AI Art 001",
"tokenId": "a80c89d2f9f7ad7584743587c4950a0f28514ba8054e8cec4e871747cf9c162c",
"documentUri": "ipfs://bafybeifevvtnap3m3fsjbwnvpknvzhqihl2ouuypio33f2vtwqejipgpkq",
"documentHash": "d6213e5629d01fd2cd714cde709e11b888c0ba441e05bb1cc649f8b784df5a6c",
"decimals": 0,
"mintBatonIsActive": false,
"tokensInCirculationBN": "1",
"tokensInCirculationStr": "1",
"blockCreated": 757238,
"totalBurned": "0",
"totalMinted": "1"
},
"immutableData": "ipfs://bafybeifevvtnap3m3fsjbwnvpknvzhqihl2ouuypio33f2vtwqejipgpkq",
"mutableData": "ipfs://bafybeibqnsmmh6bkf2wwextetki4tly65z4r4qkrrpl5xwgvzdzjley6wm"
}
const fungibleOffer01 = {
"messageType": 1,
"messageClass": 1,
"tokenId": "a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2",
"buyOrSell": "sell",
"rateInBaseUnit": 7672,
"minUnitsToExchange": 7672,
"numTokens": 1,
"utxoTxid": "822dc4c765cc68314479c7707bba28028eacab6013bb683b1680dbcc9c95b286",
"utxoVout": 1,
"timestamp": 1662930501091,
"globaltimestamp": "2022-09-11T21:08:21.091Z",
"localTimestamp": "9/11/2022, 9:08:21 PM",
"txid": "7ee0ec32e116b068ff43c5516fa500058d14f53037ba5cc729e78eb51d1f1ffd",
"p2wdbHash": "zdpuB2gHDvmByfvPFLJgEAaRyUi1cwnD45Qt66y4f5tnMXFo7",
"offerStatus": "posted",
"makerAddr": "bitcoincash:qrqlz63cwmu0hcmsrfnd8jemn3atkpaqds6tf4ksrr",
"ticker": "TROUT",
"tokenType": 1
}
const fungibleTokenData01 = {
"genesisData": {
"type": 1,
"ticker": "TROUT",
"name": "Trout's test token",
"tokenId": "a4fb5c2da1aa064e25018a43f9165040071d9e984ba190c222a7f59053af84b2",
"documentUri": "troutsblog.com",
"documentHash": "",
"decimals": 2,
"mintBatonIsActive": true,
"tokensInCirculationBN": "100097954686",
"tokensInCirculationStr": "100097954686",
"blockCreated": 622414,
"totalBurned": "2045314",
"totalMinted": "100100000000"
},
"immutableData": "troutsblog.com",
"mutableData": ""
}
module.exports = {
nftOffer01,
nftTokenData01,
simpleNftOffer01,
simpleNftTokenData01,
fungibleOffer01,
fungibleTokenData01
}
@@ -13,6 +13,7 @@ const sinon = require('sinon')
const OfferLib = require('../../../src/use-cases/offer')
const OrderUseCase = require('../../../src/use-cases/order')
const adapters = require('../mocks/adapters')
const mockData = require('../mocks/use-cases/offer-mock-data.js')
describe('#offer-use-case', () => {
let uut
@@ -73,6 +74,9 @@ describe('#offer-use-case', () => {
// Mock dependencies
sandbox.stub(uut.adapters.bchjs.Blockchain, 'getTxOut').resolves(null)
sandbox.stub(uut.adapters.wallet.bchWallet, 'getTokenData').resolves({})
sandbox.stub(uut, 'categorizeToken').resolves('nft')
sandbox.stub(uut, 'detectNsfw').resolves(false)
const result = await uut.createOffer(offerObj)
// console.log('result: ', result)
@@ -118,6 +122,9 @@ describe('#offer-use-case', () => {
},
coinbase: false
})
sandbox.stub(uut, 'categorizeToken').resolves('fungible')
sandbox.stub(uut.adapters.wallet.bchWallet, 'getTokenData').resolves({})
sandbox.stub(uut, 'detectNsfw').resolves(false)
const result = await uut.createOffer(offerObj)
// console.log('result: ', result)
@@ -125,4 +132,42 @@ describe('#offer-use-case', () => {
assert.equal(result, true)
})
})
describe('#categorizeToken', () => {
it('should categorize an NFT', async () => {
// Mock dependencies
// sandbox.stub(uut.adapters.wallet.bchWallet, 'getTokenData').resolves(mockData.nftTokenData01)
const offerData = mockData.nftOffer01
const tokenData = mockData.nftTokenData01
const result = await uut.categorizeToken(offerData, tokenData)
assert.equal(result, 'nft')
})
it('should categorize a simple NFT', async () => {
// Mock dependencies
// sandbox.stub(uut.adapters.wallet.bchWallet, 'getTokenData').resolves(mockData.simpleNftTokenData01)
const offerData = mockData.simpleNftOffer01
const tokenData = mockData.simpleNftTokenData01
const result = await uut.categorizeToken(offerData, tokenData)
assert.equal(result, 'simple-nft')
})
it('should categorize a fungible token', async () => {
// Mock dependencies
// sandbox.stub(uut.adapters.wallet.bchWallet, 'getTokenData').resolves(mockData.fungibleTokenData01)
const offerData = mockData.fungibleOffer01
const tokenData = mockData.fungibleTokenData01
const result = await uut.categorizeToken(offerData, tokenData)
assert.equal(result, 'fungible')
})
})
})