mirror of
https://github.com/Permissionless-Software-Foundation/bch-dex.git
synced 2026-09-22 01:02:00 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b611663379 | ||
|
|
03b7ed980a | ||
|
|
bf74d50bb0 | ||
|
|
003480bcb5 | ||
|
|
9dbd18d0d6 | ||
|
|
b3ba7f03e4 | ||
|
|
0a8bb52241 | ||
|
|
5eeafd7ead | ||
|
|
df146382cb | ||
|
|
af8a7c3ff3 | ||
|
|
ed6d2af23c | ||
|
|
4f0ad71fff | ||
|
|
6871380288 | ||
|
|
285456656c | ||
|
|
179d8f1656 | ||
|
|
94241c08ce | ||
|
|
abd4e872bf | ||
|
|
81521151f4 | ||
|
|
5a819c1d9c | ||
|
|
f2f3208fda | ||
|
|
7fe888cdda | ||
|
|
db3f1bf2cf | ||
|
|
5f64257dca | ||
|
|
2fbdccb883 | ||
|
|
ecc10afac3 | ||
|
|
4fc54aa9c5 | ||
|
|
2bd7f0c45c | ||
|
|
900423339e | ||
|
|
d907afa6bb | ||
|
|
9b116d82b7 | ||
|
|
1d1bf50ee3 | ||
|
|
5002004697 | ||
|
|
f311a3b9c6 | ||
|
|
fe91f4b875 | ||
|
|
2d9a2b811c | ||
|
|
514b65ed24 | ||
|
|
0c7f3088a8 | ||
|
|
12cab02300 | ||
|
|
04a908261a | ||
|
|
83898742f1 | ||
|
|
1795918719 | ||
|
|
6894217078 | ||
|
|
3ffbed27b5 | ||
|
|
b372e3d260 | ||
|
|
707b385b8a | ||
|
|
6cb140a491 | ||
|
|
c43ee8c25a | ||
|
|
0a7ec11510 | ||
|
|
f7aaba71d1 | ||
|
|
418a2e2ad2 | ||
|
|
a1023e8224 | ||
|
|
a32ed63d47 | ||
|
|
934fb46ffc | ||
|
|
b27136d03b | ||
|
|
60a3d67c0b | ||
|
|
a03c5d8c91 | ||
|
|
bcc90da49b | ||
|
|
b473a3803c | ||
|
|
9614f186b4 | ||
|
|
0390f2d956 | ||
|
|
ba5ead998d | ||
|
|
409a0f1049 | ||
|
|
bd2f27bb64 | ||
|
|
55d8d131cf | ||
|
|
5a6e3247e8 | ||
|
|
2a522775eb | ||
|
|
d677c83795 | ||
|
|
7cffd9f377 | ||
|
|
0a406cb9a4 | ||
|
|
f172a51016 | ||
|
|
00d53659b1 | ||
|
|
b0797d92f5 | ||
|
|
48484ed7af | ||
|
|
6a4aadde8a | ||
|
|
79e3aba26e | ||
|
|
bf61359bc7 | ||
|
|
a79d152993 | ||
|
|
ce7c3a0e2f | ||
|
|
e6b0580209 | ||
|
|
1f3c639100 | ||
|
|
e6efc4f49b | ||
|
|
dbbd321029 | ||
|
|
439dbc54ae | ||
|
|
547e59f52f | ||
|
|
a0d385de73 | ||
|
|
bec136009f | ||
|
|
98b7fa7734 | ||
|
|
d7bb476858 | ||
|
|
18c71c383f | ||
|
|
de85a933c4 | ||
|
|
16247372f1 | ||
|
|
c6c8b6418c | ||
|
|
10c4e7380f | ||
|
|
4933dce472 | ||
|
|
80c2822716 | ||
|
|
f48ff9a67e | ||
|
|
4bd2385359 | ||
|
|
be71492773 | ||
|
|
bcb70edf2b | ||
|
|
07071032bc | ||
|
|
f476e119a2 | ||
|
|
0fed232428 | ||
|
|
0273510485 | ||
|
|
a62dee37f4 | ||
|
|
8d6af3e1b7 | ||
|
|
ee0f2da641 | ||
|
|
44499d9652 | ||
|
|
365a5dcd4b | ||
|
|
7f5b456018 | ||
|
|
c7217486bd | ||
|
|
050141d31e | ||
|
|
b697f37199 | ||
|
|
ceb58caba1 | ||
|
|
2724b500ee | ||
|
|
1b84e7beb8 | ||
|
|
383900d8b5 | ||
|
|
ba806d86b5 | ||
|
|
4d7c7350a0 | ||
|
|
adc6500aed | ||
|
|
eb9fce73eb | ||
|
|
ec3ed165e4 | ||
|
|
d41d20cc81 | ||
|
|
5f965e7146 | ||
|
|
f382bb4578 | ||
|
|
ba37f87f80 | ||
|
|
eaa9c23f55 | ||
|
|
ac3730dbd9 | ||
|
|
581b8eb626 | ||
|
|
cd4ec2711e | ||
|
|
e9e3dbad0d | ||
|
|
295b361431 | ||
|
|
ad60df9d51 | ||
|
|
6ae635c21d | ||
|
|
61807a2f3d | ||
|
|
16ba5a8980 | ||
|
|
99b817549e | ||
|
|
60a8c335c8 | ||
|
|
1783768c32 | ||
|
|
b3630eaa06 | ||
|
|
d691016911 | ||
|
|
6506aead8c | ||
|
|
ddfc83d026 | ||
|
|
fe42c1e0e3 | ||
|
|
928a211a63 | ||
|
|
8bff69a4ca | ||
|
|
5fced7d501 | ||
|
|
7cdf622ad6 | ||
|
|
afd8ceac7b | ||
|
|
5d63db3662 | ||
|
|
9f6cd7dbf1 | ||
|
|
0725aa3ada | ||
|
|
3eb88d4076 | ||
|
|
1c61f150b1 | ||
|
|
4c74b287d8 | ||
|
|
264d762f74 | ||
|
|
3c02461c6b | ||
|
|
a30f2be51f | ||
|
|
0690a9fb65 | ||
|
|
4fa103765e | ||
|
|
b0d6ce40a9 | ||
|
|
c57a3d935b | ||
|
|
82b5943601 | ||
|
|
41b76967f5 | ||
|
|
e4c2ee44c1 | ||
|
|
c05f022995 | ||
|
|
7723c3a201 | ||
|
|
8e338818ca | ||
|
|
ea51aa420c | ||
|
|
bc410678b4 | ||
|
|
a42c15c969 | ||
|
|
a3102a0278 | ||
|
|
f4f142dc48 | ||
|
|
e03a70c9a3 | ||
|
|
d47b8797d4 | ||
|
|
56ba0aad02 | ||
|
|
825bc6d2de | ||
|
|
18fe63a5ad | ||
|
|
d6e0e7b903 | ||
|
|
8759e1af5e | ||
|
|
b78f3fb08b | ||
|
|
5fa0b2e1c8 | ||
|
|
6a88d0559a | ||
|
|
314fdc2bb0 | ||
|
|
acd7154d41 | ||
|
|
63a1bea60d | ||
|
|
077c60bd34 | ||
|
|
6485537207 | ||
|
|
286f079679 | ||
|
|
075818d551 | ||
|
|
b0f42d613b | ||
|
|
8e5b87f142 | ||
|
|
92483ee5e1 | ||
|
|
a80b82da7a | ||
|
|
41d2aaa16d | ||
|
|
9de2caba94 | ||
|
|
a45fb3ad08 | ||
|
|
666f954529 | ||
|
|
7d2e02fe64 | ||
|
|
9912ed7af7 | ||
|
|
441d898ae7 | ||
|
|
553a7ff86a | ||
|
|
349d070273 | ||
|
|
b6dcb9c177 | ||
|
|
8aff3a6568 | ||
|
|
9e885b8bc3 | ||
|
|
d6b924a5f4 | ||
|
|
97691a0dd9 | ||
|
|
3781af6305 | ||
|
|
cb5d5a603d | ||
|
|
81713bfebe | ||
|
|
b9aeb8faa0 | ||
|
|
a4e75cdccc | ||
|
|
f74c94ef50 | ||
|
|
bfb51fdcc6 | ||
|
|
25aaf4146f | ||
|
|
8ee10f2f8c | ||
|
|
bf96821c2c | ||
|
|
9787806427 | ||
|
|
9043aab432 | ||
|
|
58989a81dc | ||
|
|
82acd623d8 | ||
|
|
e4fe8cc492 | ||
|
|
0f8a6b9299 | ||
|
|
4f2c87bbd4 | ||
|
|
66b8e6b263 | ||
|
|
cabff83222 | ||
|
|
d94b527780 | ||
|
|
f0e8a05f08 | ||
|
|
278b4127e1 | ||
|
|
87b3d7519c | ||
|
|
ed4e75c9ad | ||
|
|
b0cc5a6e0b | ||
|
|
6b4139a29e | ||
|
|
925242262a | ||
|
|
74fe77009c | ||
|
|
51f7905dad | ||
|
|
0b1d3e63f6 | ||
|
|
549fd4fd5a | ||
|
|
846ee21c48 | ||
|
|
6654836440 | ||
|
|
a25a13673e | ||
|
|
dd7db3814a | ||
|
|
fdb3b9c3ac | ||
|
|
30eed1ffb1 | ||
|
|
7884cffdc6 | ||
|
|
c1a8210fa7 | ||
|
|
9f3b482f57 | ||
|
|
9487027852 | ||
|
|
9065900608 | ||
|
|
2da319f896 | ||
|
|
449a98d80c | ||
|
|
2343120fcf | ||
|
|
2d4fa523cd | ||
|
|
c8cc3adbc5 | ||
|
|
169e0ad3d2 | ||
|
|
e20a0a5d27 | ||
|
|
6dda29bb14 | ||
|
|
dd662e9fe9 | ||
|
|
9b775c5bf6 | ||
|
|
2047673862 | ||
|
|
5b545aebeb | ||
|
|
1e75816125 | ||
|
|
455fd807a4 | ||
|
|
8b75ac0f32 | ||
|
|
f1a5ae70e2 | ||
|
|
46bd532eeb | ||
|
|
6f99b215cd | ||
|
|
3482540fc8 | ||
|
|
fb999c7bd4 | ||
|
|
a09e5a5ba7 | ||
|
|
ba14b97258 | ||
|
|
79ac3729c9 | ||
|
|
bd77754686 | ||
|
|
b3b5322226 | ||
|
|
ec1f950ffb | ||
|
|
b18bc51885 | ||
|
|
e8c35dd1f3 | ||
|
|
8be0accdca | ||
|
|
94c56a06b7 | ||
|
|
42f64e3304 | ||
|
|
cc9bbc3c5d | ||
|
|
0c6696875b | ||
|
|
37b2e426a5 | ||
|
|
f3713985ed | ||
|
|
4ed9994414 | ||
|
|
140707ce6b | ||
|
|
c289e82b0e | ||
|
|
84210d07c4 | ||
|
|
6d285c77cd | ||
|
|
7cefbd71ef | ||
|
|
85d6aa7d46 | ||
|
|
aaee25389f | ||
|
|
ce83ddcadf | ||
|
|
1b961905e0 | ||
|
|
e757bd0fe0 | ||
|
|
3da78ce46b | ||
|
|
c9d1d79e11 | ||
|
|
ce118069eb | ||
|
|
f8dce21d95 | ||
|
|
e8104a8a12 | ||
|
|
2c43793bf2 | ||
|
|
c13a5d1593 | ||
|
|
6d7b3db370 | ||
|
|
33d06e99e8 | ||
|
|
3203e56a4d | ||
|
|
ff47f1b356 | ||
|
|
d9377ea78b | ||
|
|
d266bcc54b | ||
|
|
a033fb1042 | ||
|
|
569dcbd576 | ||
|
|
b0a2d73540 | ||
|
|
0d4c8cd56e | ||
|
|
b37dae60d1 | ||
|
|
448b028bb4 |
@@ -69,5 +69,9 @@ ipfs-service-provider.sh
|
||||
run-dev.sh
|
||||
wallet.json
|
||||
data/
|
||||
run-dev.sh
|
||||
*.zip
|
||||
|
||||
!README.md
|
||||
|
||||
wallet.json
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
# Permissionless Software Foundation Community Contributing Guide 1.0
|
||||
|
||||
This document describes a very simple process suitable for most projects under
|
||||
the PSF umbrella. It is based on [this Medium article](https://medium.com/the-node-js-collection/healthy-open-source-967fa8be7951) and the [Node.js Community Contribution Guide](https://github.com/nodejs/TSC/blob/master/BasePolicies/CONTRIBUTING.md).
|
||||
Projects are encouraged to adopt this whether they
|
||||
are hosted under the PSF or not.
|
||||
|
||||
The goal of this document is to create a contribution process that:
|
||||
|
||||
* Encourages new contributions.
|
||||
* Encourages contributors to remain involved.
|
||||
* Avoids unnecessary processes and bureaucracy whenever possible.
|
||||
* Creates a transparent decision making process which makes it clear how
|
||||
contributors can be involved in decision making.
|
||||
|
||||
This document is based on much prior art in the Node.js community, io.js,
|
||||
and the Node.js project.
|
||||
|
||||
Additional guidance can be found at the [Permissionless Software Foundation Telegram Channel](https://t.me/permissionless_software).
|
||||
|
||||
## Vocabulary
|
||||
|
||||
* A **Contributor** is any individual creating or commenting on an issue or pull request.
|
||||
* A **Committer** is a subset of contributors who have been given write access to the repository.
|
||||
* A **TC (Technical Committee)** is a group of committers representing the required technical
|
||||
expertise to resolve rare disputes.
|
||||
|
||||
# Logging Issues
|
||||
|
||||
Log an issue for any question or problem you might have. When in doubt, log an issue,
|
||||
any additional policies about what to include will be provided in the responses. The only
|
||||
exception is security disclosures which should be sent privately.
|
||||
|
||||
Committers may direct you to another repository, ask for additional clarifications, and
|
||||
add appropriate metadata before the issue is addressed.
|
||||
|
||||
Please be courteous, respectful, and every participant is expected to follow the
|
||||
project's Code of Conduct.
|
||||
|
||||
# Contributions
|
||||
|
||||
Any change to resources in this repository must be through pull requests. This applies to all changes
|
||||
to documentation, code, binary files, etc. Even long term committers and TC members must use
|
||||
pull requests.
|
||||
|
||||
No pull request can be merged without being reviewed.
|
||||
|
||||
For non-trivial contributions, pull requests should sit for at least 36 hours to ensure that
|
||||
contributors in other timezones have time to review. Consideration should also be given to
|
||||
weekends and other holiday periods to ensure active committers all have reasonable time to
|
||||
become involved in the discussion and review process if they wish.
|
||||
|
||||
The default for each contribution is that it is accepted once no committer has an objection.
|
||||
During review committers may also request that a specific contributor who is most versed in a
|
||||
particular area gives a "LGTM" before the PR can be merged. There is no additional "sign off"
|
||||
process for contributions to land. Once all issues brought by committers are addressed it can
|
||||
be landed by any committer.
|
||||
|
||||
In the case of an objection being raised in a pull request by another committer, all involved
|
||||
committers should seek to arrive at a consensus by way of addressing concerns being expressed
|
||||
by discussion, compromise on the proposed change, or withdrawal of the proposed change.
|
||||
|
||||
If a contribution is controversial and committers cannot agree about how to get it to land
|
||||
or if it should land then it should be escalated to the TC. TC members should regularly
|
||||
discuss pending contributions in order to find a resolution. It is expected that only a
|
||||
small minority of issues be brought to the TC for resolution and that discussion and
|
||||
compromise among committers be the default resolution mechanism.
|
||||
|
||||
# Becoming a Committer
|
||||
|
||||
All contributors who land a non-trivial contribution should be on-boarded in a timely manner,
|
||||
and added as a committer, and be given write access to the repository.
|
||||
|
||||
Committers are expected to follow this policy and continue to send pull requests, go through
|
||||
proper review, and have other committers merge their pull requests.
|
||||
|
||||
# TC Process
|
||||
|
||||
The TC uses a "consensus seeking" process for issues that are escalated to the TC.
|
||||
The group tries to find a resolution that has no open objections among TC members.
|
||||
If a consensus cannot be reached that has no objections then a majority wins vote
|
||||
is called. It is also expected that the majority of decisions made by the TC are via
|
||||
a consensus seeking process and that voting is only used as a last-resort.
|
||||
|
||||
Resolution may involve returning the issue to committers with suggestions on how to
|
||||
move forward towards a consensus. It is not expected that a meeting of the TC
|
||||
will resolve all issues on its agenda during that meeting and may prefer to continue
|
||||
the discussion happening among the committers.
|
||||
|
||||
Members can be added to the TC at any time. Any committer can nominate another committer
|
||||
to the TC and the TC uses its standard consensus seeking process to evaluate whether or
|
||||
not to add this new member. Members who do not participate consistently at the level of
|
||||
a majority of the other members are expected to resign.
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
The MIT License (MIT)
|
||||
Copyright (c) 2022 Permissionless Software Foundation
|
||||
Copyright (c) 2021-2024 Permissionless Software Foundation
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
# Pedigree
|
||||
|
||||
This repository was forked from [koa-api-boilerplate](https://github.com/christroutner/koa-api-boilerplate). Code changes from that upstream repository is frequently pulled in and merged to this repository.
|
||||
+37
-37
@@ -8,31 +8,26 @@
|
||||
*/
|
||||
|
||||
// npm libraries
|
||||
const Koa = require('koa')
|
||||
const bodyParser = require('koa-bodyparser')
|
||||
const convert = require('koa-convert')
|
||||
const logger = require('koa-logger')
|
||||
const mongoose = require('mongoose')
|
||||
const session = require('koa-generic-session')
|
||||
const passport = require('koa-passport')
|
||||
const mount = require('koa-mount')
|
||||
const serve = require('koa-static')
|
||||
const cors = require('kcors')
|
||||
import Koa from 'koa'
|
||||
|
||||
import bodyParser from 'koa-bodyparser'
|
||||
import convert from 'koa-convert'
|
||||
import logger from 'koa-logger'
|
||||
import mongoose from 'mongoose'
|
||||
import session from 'koa-generic-session'
|
||||
import passport from 'koa-passport'
|
||||
import mount from 'koa-mount'
|
||||
import serve from 'koa-static'
|
||||
import cors from 'kcors'
|
||||
|
||||
// Local libraries
|
||||
const config = require('../config') // this first.
|
||||
const AdminLib = require('../src/adapters/admin')
|
||||
// const adminLib = new AdminLib()
|
||||
import config from '../config/index.js' // this first.
|
||||
|
||||
// const WebHookLib = require('../src/adapters/webhook')
|
||||
// const webhookLib = new WebHookLib()
|
||||
|
||||
// const JSONRPC = require('../src/rpc')
|
||||
// const rpc = new JSONRPC()
|
||||
|
||||
const errorMiddleware = require('../src/controllers/rest-api/middleware/error')
|
||||
// const { wlogger } = require('../src/adapters/wlogger')
|
||||
const Controllers = require('../src/controllers')
|
||||
import AdminLib from '../src/adapters/admin.js'
|
||||
import errorMiddleware from '../src/controllers/rest-api/middleware/error.js'
|
||||
// import wlogger from '../src/adapters/wlogger.js'
|
||||
import Controllers from '../src/controllers/index.js'
|
||||
import { applyPassportMods } from '../config/passport.js'
|
||||
|
||||
class Server {
|
||||
constructor () {
|
||||
@@ -50,16 +45,18 @@ class Server {
|
||||
const app = new Koa()
|
||||
app.keys = [this.config.session]
|
||||
|
||||
// Connect to the Mongo Database.
|
||||
this.mongoose.Promise = global.Promise
|
||||
this.mongoose.set('useCreateIndex', true) // Stop deprecation warning.
|
||||
console.log(
|
||||
`Connecting to MongoDB with this connection string: ${this.config.database}`
|
||||
)
|
||||
await this.mongoose.connect(this.config.database, {
|
||||
useUnifiedTopology: true,
|
||||
useNewUrlParser: true
|
||||
})
|
||||
if (!this.config.noMongo) {
|
||||
// Connect to the Mongo Database.
|
||||
this.mongoose.Promise = global.Promise
|
||||
this.mongoose.set('useCreateIndex', true) // Stop deprecation warning.
|
||||
console.log(
|
||||
`Connecting to MongoDB with this connection string: ${this.config.database}`
|
||||
)
|
||||
await this.mongoose.connect(this.config.database, {
|
||||
useUnifiedTopology: true,
|
||||
useNewUrlParser: true
|
||||
})
|
||||
}
|
||||
|
||||
console.log(`Starting environment: ${this.config.env}`)
|
||||
console.log(`Debug level: ${this.config.debugLevel}\n`)
|
||||
@@ -81,7 +78,8 @@ class Server {
|
||||
app.use(mount('/logs', serve(`${process.cwd()}/config/logs`)))
|
||||
|
||||
// User Authentication
|
||||
require('../config/passport')
|
||||
// require('../config/passport')
|
||||
applyPassportMods(passport)
|
||||
app.use(passport.initialize())
|
||||
app.use(passport.session())
|
||||
|
||||
@@ -128,9 +126,11 @@ class Server {
|
||||
this.server = await app.listen(config.port)
|
||||
console.log(`Server started on ${config.port}`)
|
||||
|
||||
// Create the system admin user.
|
||||
const success = await this.adminLib.createSystemUser()
|
||||
if (success) console.log('System admin user created.')
|
||||
if (!this.config.noMongo) {
|
||||
// Create the system admin user.
|
||||
const success = await this.adminLib.createSystemUser()
|
||||
if (success) console.log('System admin user created.')
|
||||
}
|
||||
|
||||
// Attach the other IPFS controllers.
|
||||
// Skip if this is a test environment.
|
||||
@@ -161,4 +161,4 @@ class Server {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Server
|
||||
export default Server
|
||||
|
||||
Vendored
+89
-14
@@ -1,19 +1,31 @@
|
||||
/*
|
||||
This file is used to store unsecure, application-specific data common to all
|
||||
environments.
|
||||
|
||||
Additional Environent Variables:
|
||||
- CONNECT_PREF: should have a value of 'cr' (default), or 'direct'. This is
|
||||
used by helia-coord to select a connection preference between peers. Servers
|
||||
with an ip4 or ip6 address should use 'direct'.
|
||||
*/
|
||||
|
||||
/* eslint no-unneeded-ternary:0 */
|
||||
|
||||
// Hack to get __dirname back.
|
||||
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
|
||||
import * as url from 'url'
|
||||
|
||||
// Get the version from the package.json file.
|
||||
const pkgInfo = require('../../package.json')
|
||||
import { readFileSync } from 'fs'
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||
const pkgInfo = JSON.parse(readFileSync(`${__dirname.toString()}/../../package.json`))
|
||||
|
||||
const version = pkgInfo.version
|
||||
|
||||
const ipfsCoordName = process.env.COORD_NAME
|
||||
? process.env.COORD_NAME
|
||||
: 'ipfs-torlist-service-generic'
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
// Configure TCP port.
|
||||
port: process.env.PORT || 5700,
|
||||
|
||||
@@ -40,23 +52,70 @@ module.exports = {
|
||||
|
||||
// P2WDB URL that will accept API calls from the p2wdb npm library.
|
||||
p2wdbUrl: process.env.P2WDB_URL ? process.env.P2WDB_URL : 'https://p2wdb.fullstack.cash',
|
||||
// p2wdbUrl: process.env.P2WDB_URL ? process.env.P2WDB_URL : 'http://localhost:5010',
|
||||
|
||||
// Enable or Disable the usage of Mongo DB.
|
||||
noMongo: process.env.NO_MONGO ? true : false,
|
||||
|
||||
// BEGIN WALLET CONFIGURATION
|
||||
|
||||
// BCH Mnemonic for generating encryption keys and payment address
|
||||
mnemonic: process.env.MNEMONIC ? process.env.MNEMONIC : '',
|
||||
|
||||
// (Optional) use a wallet file generated by psf-bch-wallet.
|
||||
walletFile: process.env.WALLET_FILE
|
||||
? process.env.WALLET_FILE
|
||||
: `${__dirname.toString()}/../../wallet.json`,
|
||||
|
||||
// Wallet Configuration
|
||||
// Default value: web3 - connect to bch-api through web 3 JSON RPC over IPFS.
|
||||
// Alternate value: web2 - connect to bch-api through a web 2 REST API over HTTP.
|
||||
walletInterface: process.env.WALLET_INTERFACE ? process.env.WALLET_INTERFACE : 'web3',
|
||||
|
||||
// Wallet API Server
|
||||
// Default web2 server: https://api.fullstack.cash/v5/
|
||||
// Default web3 server: https://free-bch.fullstack.cash/
|
||||
apiServer: process.env.WALLET_INTERFACE === 'web2'
|
||||
? (
|
||||
process.env.APISERVER
|
||||
? process.env.APISERVER
|
||||
: 'https://api.fullstack.cash/v5/'
|
||||
)
|
||||
: 'https://free-bch.fullstack.cash/',
|
||||
|
||||
// Basic Authentication Password for connecting to bch-api through a web 2
|
||||
// REST API over HTTP.
|
||||
authPass: process.env.WALLET_AUTH_PASS
|
||||
? process.env.WALLET_AUTH_PASS
|
||||
: '',
|
||||
|
||||
// FullStack.cash account information, used for automatic JWT handling.
|
||||
getJwtAtStartup: process.env.GET_JWT_AT_STARTUP ? true : false,
|
||||
authServer: process.env.AUTHSERVER
|
||||
? process.env.AUTHSERVER
|
||||
: 'https://auth.fullstack.cash',
|
||||
apiServer: process.env.APISERVER
|
||||
? process.env.APISERVER
|
||||
: 'https://api.fullstack.cash/v5/',
|
||||
fullstackLogin: process.env.FULLSTACKLOGIN
|
||||
? process.env.FULLSTACKLOGIN
|
||||
: 'demo@demo.com',
|
||||
fullstackPassword: process.env.FULLSTACKPASS
|
||||
? process.env.FULLSTACKPASS
|
||||
: 'demo',
|
||||
// authPass: process.env.FULLSTACK_AUTH_PASS
|
||||
// ? process.env.FULLSTACK_AUTH_PASS
|
||||
// : '',
|
||||
|
||||
// END WALLET CONFIGURATION
|
||||
|
||||
// BEGIN IPFS CONFIGURATION
|
||||
|
||||
// Debug verbosity level of helia-coord.
|
||||
// 0 = no debug logs. 3 = maximum logs.
|
||||
debugLevel: process.env.DEBUG_LEVEL ? parseInt(process.env.DEBUG_LEVEL) : 2,
|
||||
|
||||
// Enable/Disable the IPFS node at startup. Enabled by default.
|
||||
// useIpfs: process.env.DISABLE_IPFS ? false : true,
|
||||
useIpfs: false,
|
||||
|
||||
// IPFS settings.
|
||||
isCircuitRelay: process.env.ENABLE_CIRCUIT_RELAY ? true : false,
|
||||
// SSL domain used for websocket connection via browsers.
|
||||
crDomain: process.env.CR_DOMAIN ? process.env.CR_DOMAIN : '',
|
||||
@@ -77,7 +136,10 @@ module.exports = {
|
||||
'@type': 'Organization',
|
||||
name: 'Permissionless Software Foundation',
|
||||
url: 'https://PSFoundation.cash'
|
||||
}
|
||||
},
|
||||
|
||||
// If this node has an IP4 address or domain name used to provide a REST API.
|
||||
web2Api: process.env.WEB2_API ? process.env.WEB2_API : null
|
||||
},
|
||||
|
||||
// P2WDB webhook endpoint
|
||||
@@ -94,17 +156,30 @@ module.exports = {
|
||||
ipfsTcpPort: process.env.IPFS_TCP_PORT ? process.env.IPFS_TCP_PORT : 4001,
|
||||
ipfsWsPort: process.env.IPFS_WS_PORT ? process.env.IPFS_WS_PORT : 4003,
|
||||
|
||||
// BCH Mnemonic for generating encryption keys and payment address
|
||||
mnemonic: process.env.MNEMONIC ? process.env.MNEMONIC : '',
|
||||
|
||||
debugLevel: process.env.DEBUG_LEVEL ? parseInt(process.env.DEBUG_LEVEL) : 1,
|
||||
|
||||
// Settings for production, using external go-ipfs node.
|
||||
isProduction: process.env.SVC_ENV === 'production' ? true : false,
|
||||
isProduction: process.env.SVC_ENV === 'prod' ? true : false,
|
||||
ipfsHost: process.env.IPFS_HOST ? process.env.IPFS_HOST : 'localhost',
|
||||
ipfsApiPort: process.env.IPFS_API_PORT
|
||||
? parseInt(process.env.IPFS_API_PORT)
|
||||
: 5001,
|
||||
|
||||
chatPubSubChan: 'psf-ipfs-chat-001'
|
||||
chatPubSubChan: 'psf-ipfs-chat-001',
|
||||
|
||||
// IPFS gateway
|
||||
ipfsGateway: process.env.IPFS_GATEWAY ? process.env.IPFS_GATEWAY : 'https://pin.fullstack.cash/ipfs/download/',
|
||||
|
||||
// This can add specific Circuit Relay v2 servers to connect to.
|
||||
bootstrapRelays: [
|
||||
// v2 Circuit Relay (Token Tiger)
|
||||
// '/ip4/137.184.93.145/tcp/8001/p2p/12D3KooWGMEKkdJfyZbwdH9EafZbRTtMn7FnhWPrE4MhRty2763g',
|
||||
|
||||
// v2 Circuit Relay server (FullStack.cash)
|
||||
// '/ip4/78.46.129.7/tcp/4001/p2p/12D3KooWFQ11GQ5NubsJGhYZ4X3wrAGimLevxfm6HPExCrMYhpSL'
|
||||
],
|
||||
|
||||
// END IPFS CONFIGURATION
|
||||
|
||||
// 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'
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -4,7 +4,7 @@
|
||||
specified.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
session: 'secret-boilerplate-token',
|
||||
token: 'secret-jwt-token',
|
||||
database: 'mongodb://localhost:27017/bch-swap-service-dev',
|
||||
|
||||
Vendored
+1
-1
@@ -7,7 +7,7 @@
|
||||
so as not to conflict with the default host port of 27017 for MongoDB.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
session: 'secret-boilerplate-token',
|
||||
token: 'secret-jwt-token',
|
||||
// database: 'mongodb://172.17.0.1:5555/ipfs-service-prod',
|
||||
|
||||
Vendored
+3
-2
@@ -4,9 +4,10 @@
|
||||
This is the environment run by the test suite.
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
export default {
|
||||
session: 'secret-boilerplate-token',
|
||||
token: 'secret-jwt-token',
|
||||
database: 'mongodb://localhost:27017/bch-swap-service-test',
|
||||
env: 'test'
|
||||
env: 'test',
|
||||
nostrRelay: 'wss://'
|
||||
}
|
||||
|
||||
+14
-3
@@ -1,6 +1,17 @@
|
||||
const common = require('./env/common')
|
||||
import common from './env/common.js'
|
||||
|
||||
import development from './env/development.js'
|
||||
import production from './env/production.js'
|
||||
import test from './env/test.js'
|
||||
|
||||
const env = process.env.BCH_DEX || 'development'
|
||||
const config = require(`./env/${env}`)
|
||||
console.log(`Loading config for this environment: ${env}`)
|
||||
|
||||
module.exports = Object.assign({}, common, config)
|
||||
let config = development
|
||||
if (env === 'test') {
|
||||
config = test
|
||||
} else if (env === 'prod') {
|
||||
config = production
|
||||
}
|
||||
|
||||
export default Object.assign({}, common, config)
|
||||
|
||||
+32
-29
@@ -1,30 +1,6 @@
|
||||
const passport = require('koa-passport')
|
||||
const User = require('../src/adapters/localdb/models/users')
|
||||
const Strategy = require('passport-local')
|
||||
|
||||
passport.serializeUser((user, done) => {
|
||||
done(null, user.id)
|
||||
})
|
||||
|
||||
passport.deserializeUser(async (id, done) => {
|
||||
try {
|
||||
const user = await User.findById(id, '-password')
|
||||
done(null, user)
|
||||
} catch (err) {
|
||||
done(err)
|
||||
}
|
||||
})
|
||||
|
||||
passport.use(
|
||||
'local',
|
||||
new Strategy(
|
||||
{
|
||||
usernameField: 'email',
|
||||
passwordField: 'password'
|
||||
},
|
||||
passportCallback
|
||||
)
|
||||
)
|
||||
// import passport from 'koa-passport';
|
||||
import User from '../src/adapters/localdb/models/users.js'
|
||||
import Strategy from 'passport-local'
|
||||
|
||||
async function passportCallback (email, password, done) {
|
||||
try {
|
||||
@@ -49,5 +25,32 @@ async function passportCallback (email, password, done) {
|
||||
}
|
||||
}
|
||||
|
||||
// For testing
|
||||
module.exports = { passport, passportCallback }
|
||||
function applyPassportMods (passport) {
|
||||
passport.serializeUser((user, done) => {
|
||||
done(null, user.id)
|
||||
})
|
||||
|
||||
passport.deserializeUser(async (id, done) => {
|
||||
try {
|
||||
const user = await User.findById(id, '-password')
|
||||
done(null, user)
|
||||
} catch (err) {
|
||||
done(err)
|
||||
}
|
||||
})
|
||||
|
||||
passport.use(
|
||||
'local',
|
||||
new Strategy(
|
||||
{
|
||||
usernameField: 'email',
|
||||
passwordField: 'password'
|
||||
},
|
||||
passportCallback
|
||||
)
|
||||
)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
export { applyPassportMods, passportCallback }
|
||||
|
||||
Binary file not shown.
Binary file not shown.
|
After Width: | Height: | Size: 27 KiB |
@@ -1,4 +1,4 @@
|
||||
const Server = require('./bin/server.js')
|
||||
import Server from './bin/server.js'
|
||||
const server = new Server()
|
||||
|
||||
server.startServer()
|
||||
|
||||
Generated
+15155
-20640
File diff suppressed because it is too large
Load Diff
+35
-15
@@ -1,20 +1,21 @@
|
||||
{
|
||||
"name": "bch-dex",
|
||||
"version": "1.0.0",
|
||||
"version": "3.0.0",
|
||||
"description": "A DEX on BCH for trading SLP tokens.",
|
||||
"main": "index.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"test": "npm run test:all",
|
||||
"test:all": "export BCH_DEX=test && nyc --reporter=text mocha --exit --timeout 30000 --recursive test/unit test/e2e/automated/",
|
||||
"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/",
|
||||
"test:integration": "export BCH_DEX=test && mocha --exit --timeout 45000 --recursive test/integration",
|
||||
"test:temp": "export BCH_DEX=test && mocha --exit --timeout 15000 -g '#rate-limit' test/unit/json-rpc/",
|
||||
"lint": "standard --env mocha --fix",
|
||||
"docs": "./node_modules/.bin/apidoc -i src/ -o docs",
|
||||
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
||||
"coverage:report": "export BCH_DEX=test && nyc --reporter=html mocha --exit --timeout 15000 --recursive test/unit/ test/e2e/automated/"
|
||||
"coverage": "c8 report --reporter=text-lcov | coveralls",
|
||||
"coverage:report": "export BCH_DEX=test && c8 --reporter=html mocha --exit --timeout 15000 --recursive test/unit/ test/e2e/automated/"
|
||||
},
|
||||
"author": "Chris Troutner <chris.troutner@gmail.com>",
|
||||
"contributors": [
|
||||
@@ -27,15 +28,34 @@
|
||||
},
|
||||
"repository": "Permissionless-Software-Foundation/bch-dex",
|
||||
"dependencies": {
|
||||
"@psf/bch-js": "6.4.5",
|
||||
"@chainsafe/libp2p-gossipsub": "13.1.1",
|
||||
"@chainsafe/libp2p-noise": "15.1.1",
|
||||
"@chainsafe/libp2p-yamux": "6.0.2",
|
||||
"@chris.troutner/retry-queue": "1.0.8",
|
||||
"@helia/unixfs": "3.0.7",
|
||||
"@libp2p/bootstrap": "10.1.5",
|
||||
"@libp2p/circuit-relay-v2": "1.1.5",
|
||||
"@libp2p/identify": "2.1.5",
|
||||
"@libp2p/keychain": "4.1.5",
|
||||
"@libp2p/logger": "4.0.20",
|
||||
"@libp2p/tcp": "9.1.5",
|
||||
"@libp2p/webrtc": "4.1.7",
|
||||
"@libp2p/websockets": "8.2.0",
|
||||
"@multiformats/multiaddr": "12.3.0",
|
||||
"@psf/bch-js": "6.7.3",
|
||||
"axios": "0.27.2",
|
||||
"bch-message-lib": "2.2.1",
|
||||
"bch-nostr": "1.3.1",
|
||||
"bch-token-sweep": "2.2.1",
|
||||
"bcryptjs": "2.4.3",
|
||||
"bitcoincashjs-lib": "3.3.3",
|
||||
"blockstore-fs": "2.0.0",
|
||||
"datastore-fs": "9.1.7",
|
||||
"glob": "7.1.6",
|
||||
"ipfs": "0.62.3",
|
||||
"ipfs-coord": "8.0.5",
|
||||
"ipfs-http-client": "55.0.0",
|
||||
"helia": "4.2.5",
|
||||
"helia-coord": "1.5.15",
|
||||
"ipfs-coord-esm": "9.1.7",
|
||||
"ipfs-http-client": "58.0.0",
|
||||
"jsonrpc-lite": "2.2.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"jwt-bch-lib": "1.3.0",
|
||||
@@ -50,26 +70,26 @@
|
||||
"koa-router": "10.0.0",
|
||||
"koa-static": "5.0.0",
|
||||
"koa2-ratelimit": "0.9.1",
|
||||
"libp2p": "^0.36.2",
|
||||
"libp2p": "1.9.1",
|
||||
"line-reader": "0.4.0",
|
||||
"minimal-slp-wallet": "5.1.0",
|
||||
"mongoose": "5.13.13",
|
||||
"minimal-slp-wallet": "5.12.0",
|
||||
"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.9",
|
||||
"passport-local": "1.0.0",
|
||||
"public-ip": "^4.0.4",
|
||||
"public-ip": "6.0.1",
|
||||
"winston": "3.3.3",
|
||||
"winston-daily-rotate-file": "4.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"apidoc": "0.51.1",
|
||||
"bch-token-sweep": "2.1.2",
|
||||
"c8": "7.12.0",
|
||||
"chai": "4.3.0",
|
||||
"coveralls": "2.11.4",
|
||||
"husky": "4.3.8",
|
||||
"lodash.clonedeep": "4.5.0",
|
||||
"mocha": "10.0.0",
|
||||
"nyc": "15.1.0",
|
||||
"semantic-release": "19.0.3",
|
||||
"sinon": "9.2.4",
|
||||
"standard": "17.0.0",
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
COMPOSE_PROJECT_NAME=ipfs-service-provider
|
||||
@@ -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"]
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
setInterval(function () {
|
||||
const now = new Date()
|
||||
console.log(`timestamp: ${now.toLocaleString()}`)
|
||||
}, 5000)
|
||||
@@ -24,9 +24,11 @@ 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
|
||||
|
||||
#export P2WDB_URL=http://172.17.0.1:5667
|
||||
|
||||
npm start
|
||||
|
||||
@@ -46,10 +46,11 @@ services:
|
||||
#build:
|
||||
# context: ./p2wdb/
|
||||
# dockerfile: Dockerfile
|
||||
image: christroutner/p2wdb:v3.0.9
|
||||
image: christroutner/p2wdb:v4.1.3
|
||||
container_name: p2wdb
|
||||
environment:
|
||||
CONSUMER_URL: 'https://free-bch.fullstack.cash'
|
||||
#ENABLE_BCH_PAYMENT: 1
|
||||
DEBUG_LEVEL: 1
|
||||
logging:
|
||||
driver: 'json-file'
|
||||
@@ -67,13 +68,14 @@ services:
|
||||
volumes:
|
||||
- ../data/ipfsdata/p2wdb:/home/safeuser/ipfs-p2wdb-service/.ipfsdata/p2wdb
|
||||
- ./p2wdb/start-production.sh:/home/safeuser/ipfs-p2wdb-service/start-production.sh
|
||||
#- ../scripts/wallet.json:/home/safeuser/ipfs-p2wdb-service/wallet.json
|
||||
restart: always
|
||||
|
||||
bch-dex:
|
||||
#build:
|
||||
# context: ./bch-dex/
|
||||
# dockerfile: Dockerfile
|
||||
image: christroutner/bch-dex:v1.13.0
|
||||
image: christroutner/bch-dex:v2.2.4
|
||||
container_name: bch-dex
|
||||
environment:
|
||||
CONSUMER_URL: 'https://free-bch.fullstack.cash'
|
||||
@@ -97,7 +99,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'
|
||||
@@ -106,7 +108,7 @@ services:
|
||||
options:
|
||||
max-size: '10m'
|
||||
max-file: '10'
|
||||
mem_limit: 500mb
|
||||
mem_limit: 1000mb
|
||||
links:
|
||||
- bch-dex
|
||||
ports:
|
||||
|
||||
@@ -12,9 +12,12 @@ RUN apt-get update
|
||||
RUN apt-get install -y sudo git curl nano gnupg wget
|
||||
|
||||
#Install Node and NPM
|
||||
RUN curl -sL https://deb.nodesource.com/setup_16.x -o nodesource_setup.sh
|
||||
RUN bash nodesource_setup.sh
|
||||
RUN apt-get install -y nodejs build-essential
|
||||
RUN sudo apt-get install -y ca-certificates curl gnupg
|
||||
RUN sudo mkdir -p /etc/apt/keyrings
|
||||
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
|
||||
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_20.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
|
||||
RUN sudo apt-get update
|
||||
RUN sudo apt-get install -y nodejs build-essential
|
||||
|
||||
#Create the user 'safeuser' and add them to the sudo group.
|
||||
#RUN useradd -ms /bin/bash safeuser
|
||||
@@ -34,7 +37,7 @@ WORKDIR /home/safeuser
|
||||
|
||||
# Update to the latest version of npm.
|
||||
#RUN npm install -g npm@7.23.0
|
||||
RUN npm install -g npm
|
||||
#RUN npm install -g npm
|
||||
|
||||
# npm mirror to prevent direct dependency on npm.
|
||||
RUN npm set registry http://94.130.170.209:4873/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -43,7 +43,7 @@ services:
|
||||
#build:
|
||||
# context: ./p2wdb/
|
||||
# dockerfile: Dockerfile
|
||||
image: christroutner/p2wdb-rpi:v3.0.10
|
||||
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.13.0
|
||||
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
|
||||
|
||||
@@ -0,0 +1,153 @@
|
||||
/*
|
||||
This script is similar ot sweep-wallet.js, but it can do a much faster (bulk)
|
||||
scanning of addresses. However, this requires a subscription to a FullStack.cash
|
||||
or access to a web2 Cash Stack.
|
||||
|
||||
This script will scan 20 addresses at a time. If a balance is found in any
|
||||
of the addresses, it will sweep the funds from that address.
|
||||
*/
|
||||
/*
|
||||
// Public npm libraries
|
||||
import BchTokenSweep from 'bch-token-sweep/index.js'
|
||||
|
||||
// Local libraries
|
||||
import WalletAdapter from '../../src/adapters/wallet.js'
|
||||
import BCHJS from '@psf/bch-js'
|
||||
import JwtLib from 'jwt-bch-lib'
|
||||
|
||||
// Constants
|
||||
// const EMTPY_ADDR_CUTOFF = 100
|
||||
const LAST_ADDR_INDEX = 100
|
||||
|
||||
if (!process.env.BCHJSTOKEN) {
|
||||
console.log('You will need a JWT token from FullStack.cash to execute this script. Export it to the BCHJSTOKEN environment variable and try again.')
|
||||
exit(0)
|
||||
}
|
||||
|
||||
async function sweepFunds () {
|
||||
try {
|
||||
// Configure the wallet library to use a FullStack.cash or a local Cash Stack
|
||||
const walletLib = new WalletAdapter()
|
||||
walletLib.config.useFullStackCash = true
|
||||
walletLib.config.apiServer = 'http://192.168.2.129:3000/v5/'
|
||||
|
||||
// const walletInfo = await walletLib.openWallet()
|
||||
const walletInfo = await openWallet(walletLib)
|
||||
const wallet = await walletLib.instanceWallet(walletInfo)
|
||||
console.log('walletInfo: ', walletInfo)
|
||||
|
||||
const rootAddr = walletInfo.cashAddress
|
||||
const rootWif = walletInfo.privateKey
|
||||
console.log(`Sweeping all funds into root address ${rootAddr}...`)
|
||||
|
||||
// Instantiate bch-js, and use the JWT token saved to environment variable.
|
||||
|
||||
// Generate an HD tree
|
||||
// const bchjs = wallet.bchjs
|
||||
const bchjs = new BCHJS()
|
||||
const rootSeed = await bchjs.Mnemonic.toSeed(walletInfo.mnemonic)
|
||||
const masterHDNode = bchjs.HDNode.fromSeed(rootSeed)
|
||||
|
||||
const emptyAddrCnt = 0
|
||||
const hdIndex = 1
|
||||
|
||||
// Divide all the addresses by 20, since that's how many will be checked at once.
|
||||
const numOfLoops = Math.ceil(LAST_ADDR_INDEX / 20)
|
||||
|
||||
for (let i = 1; i < numOfLoops; i++) {
|
||||
const startIndex = i
|
||||
const stopIndex = i * 20
|
||||
console.log(`startIndex: ${startIndex}, stopIndex: ${stopIndex}`)
|
||||
|
||||
// Generate the next block of 20 key pairs to scan.
|
||||
const addrsToScan = []
|
||||
const wifsToScan = []
|
||||
for (let j = startIndex; j < stopIndex; j++) {
|
||||
// Generate a keypair from the HD wallet.
|
||||
const childNode = masterHDNode.derivePath(`m/44'/245'/0'/0/${j}`)
|
||||
const cashAddress = bchjs.HDNode.toCashAddress(childNode)
|
||||
const wifToSweep = bchjs.HDNode.toWIF(childNode)
|
||||
|
||||
addrsToScan.push(cashAddress)
|
||||
wifsToScan.push(wifToSweep)
|
||||
}
|
||||
console.log(`Scanning these addresses for a balance: ${JSON.stringify(addrsToScan, null, 2)}`)
|
||||
|
||||
// Scan a block of 20 addresses for a balance.
|
||||
const balances = await bchjs.Electrumx.balance(addrsToScan)
|
||||
console.log(`balances: ${JSON.stringify(balances, null, 2)}`)
|
||||
|
||||
// Scan through the results and look for a balance.
|
||||
for (let j = 0; j < balances.balances.length; j++) {
|
||||
const thisBalance = balances.balances[j]
|
||||
|
||||
// const combinedBal = thisBalance.balance.confirmed + thisBalance.balance.
|
||||
|
||||
// if(thisBalance.)
|
||||
}
|
||||
}
|
||||
|
||||
// do {
|
||||
// // Generate a keypair from the HD wallet.
|
||||
// const childNode = masterHDNode.derivePath(`m/44'/245'/0'/0/${hdIndex}`)
|
||||
// const cashAddress = bchjs.HDNode.toCashAddress(childNode)
|
||||
// const wifToSweep = bchjs.HDNode.toWIF(childNode)
|
||||
//
|
||||
// console.log(`\nSweeping ${cashAddress} with private key ${wifToSweep}`)
|
||||
//
|
||||
// try {
|
||||
// // Sweep tokens from address
|
||||
// const sweeper = new BchTokenSweep(
|
||||
// wifToSweep,
|
||||
// rootWif,
|
||||
// wallet,
|
||||
// 550,
|
||||
// rootAddr
|
||||
// )
|
||||
// await sweeper.populateObjectFromNetwork()
|
||||
//
|
||||
// const hex = await sweeper.sweepTo(rootAddr)
|
||||
// // console.log(`hex: ${hex}`)
|
||||
//
|
||||
// const txid = await sweeper.blockchain.broadcast(hex)
|
||||
//
|
||||
// // console.log('Transaction ID', txid)
|
||||
// console.log(`Swept HD index ${hdIndex}. TXID: ${txid}`)
|
||||
//
|
||||
// emptyAddrCnt = 0
|
||||
//
|
||||
// // Wait between loop iterations.
|
||||
// await bchjs.Util.sleep(3000)
|
||||
// } catch (err) {
|
||||
// console.log(`error message with index ${hdIndex}: ${err.message}`)
|
||||
// // console.log(err)
|
||||
// emptyAddrCnt++
|
||||
// }
|
||||
//
|
||||
// hdIndex++
|
||||
// } while (emptyAddrCnt < EMTPY_ADDR_CUTOFF)
|
||||
//
|
||||
// console.log(`${EMTPY_ADDR_CUTOFF} empty addresses detected. Exiting.`)
|
||||
//
|
||||
// console.log('\n\nDo not forget to reset the nextAddress property in the wallet.json file!\n\n')
|
||||
} catch (err) {
|
||||
console.error('Error in sweepFunds(): ', err)
|
||||
}
|
||||
}
|
||||
sweepFunds()
|
||||
|
||||
// Open the wallet file, or create one if the file doesn't exist.
|
||||
// Does not instance the wallet. The output of this function is expected to
|
||||
// be passed to instanceWallet().
|
||||
async function openWallet (walletLib) {
|
||||
try {
|
||||
// console.log('this.WALLET_FILE: ', this.WALLET_FILE)
|
||||
const walletData = await walletLib.jsonFiles.readJSON('./wallet.json')
|
||||
|
||||
return walletData
|
||||
} catch (err) {
|
||||
console.error('Error in openWallet()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
*/
|
||||
@@ -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'
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
This script is the same as sweep-wallet.js, but it requires a FullStack.cash
|
||||
account, which makes the scanning much faster.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
// const BCHJS = require('@psf/bch-js')
|
||||
import BchTokenSweep from 'bch-token-sweep/index.js'
|
||||
|
||||
// Local libraries
|
||||
import WalletAdapter from '../../src/adapters/wallet.js'
|
||||
|
||||
// Constants
|
||||
// const EMTPY_ADDR_CUTOFF = 10
|
||||
const START_INDEX = 1
|
||||
const LAST_ADDR_INDEX = 100
|
||||
|
||||
if (!process.env.BCHJSTOKEN) {
|
||||
console.log('You will need a JWT token from FullStack.cash to execute this script. Export it to the BCHJSTOKEN environment variable and try again.')
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
async function sweepFunds () {
|
||||
try {
|
||||
// Configure the wallet library to use a FullStack.cash or a local Cash Stack
|
||||
const walletLib = new WalletAdapter()
|
||||
walletLib.config.useFullStackCash = true
|
||||
// walletLib.config.apiServer = 'http://192.168.2.129:3000/v5/'
|
||||
walletLib.config.apiServer = 'https://api.fullstack.cash/v5/'
|
||||
|
||||
// Open the wallet files.
|
||||
const walletInfo = await openWallet(walletLib)
|
||||
const wallet = await walletLib.instanceWallet(walletInfo)
|
||||
console.log('walletInfo: ', walletInfo)
|
||||
|
||||
const rootAddr = walletInfo.cashAddress
|
||||
const rootWif = walletInfo.privateKey
|
||||
console.log(`Sweeping all funds into root address ${rootAddr}...`)
|
||||
|
||||
// Generate an HD tree
|
||||
const bchjs = wallet.bchjs
|
||||
const rootSeed = await bchjs.Mnemonic.toSeed(walletInfo.mnemonic)
|
||||
const masterHDNode = bchjs.HDNode.fromSeed(rootSeed)
|
||||
|
||||
let emptyAddrCnt = 0
|
||||
let hdIndex = START_INDEX
|
||||
|
||||
do {
|
||||
// Generate a keypair from the HD wallet.
|
||||
const childNode = masterHDNode.derivePath(`m/44'/245'/0'/0/${hdIndex}`)
|
||||
const cashAddress = bchjs.HDNode.toCashAddress(childNode)
|
||||
const wifToSweep = bchjs.HDNode.toWIF(childNode)
|
||||
|
||||
console.log(`\nSweeping ${cashAddress} with private key ${wifToSweep}`)
|
||||
|
||||
try {
|
||||
// Sweep tokens from address
|
||||
const sweeper = new BchTokenSweep(
|
||||
wifToSweep,
|
||||
rootWif,
|
||||
wallet,
|
||||
550,
|
||||
rootAddr
|
||||
)
|
||||
await sweeper.populateObjectFromNetwork()
|
||||
|
||||
const hex = await sweeper.sweepTo(rootAddr)
|
||||
// console.log(`hex: ${hex}`)
|
||||
|
||||
const txid = await sweeper.blockchain.broadcast(hex)
|
||||
|
||||
// console.log('Transaction ID', txid)
|
||||
console.log(`Swept HD index ${hdIndex}. TXID: ${txid}`)
|
||||
|
||||
emptyAddrCnt = 0
|
||||
|
||||
// Wait between loop iterations.
|
||||
await bchjs.Util.sleep(3000)
|
||||
} catch (err) {
|
||||
console.log(`error message with index ${hdIndex}: ${err.message}`)
|
||||
// console.log(err)
|
||||
emptyAddrCnt++
|
||||
}
|
||||
|
||||
hdIndex++
|
||||
// } while (emptyAddrCnt < EMTPY_ADDR_CUTOFF)
|
||||
// console.log(`${EMTPY_ADDR_CUTOFF} empty addresses detected. Exiting.`)
|
||||
} while (hdIndex < LAST_ADDR_INDEX)
|
||||
console.log(`Last index of ${LAST_ADDR_INDEX} reached. emptyAddrCnt: ${emptyAddrCnt}`)
|
||||
|
||||
console.log('\n\nDo not forget to reset the nextAddress property in the wallet.json file!\n\n')
|
||||
} catch (err) {
|
||||
console.error('Error in sweepFunds(): ', err)
|
||||
}
|
||||
}
|
||||
sweepFunds()
|
||||
|
||||
// Open the wallet file, or create one if the file doesn't exist.
|
||||
// Does not instance the wallet. The output of this function is expected to
|
||||
// be passed to instanceWallet().
|
||||
async function openWallet (walletLib) {
|
||||
try {
|
||||
// console.log('this.WALLET_FILE: ', this.WALLET_FILE)
|
||||
const walletData = await walletLib.jsonFiles.readJSON('./wallet.json')
|
||||
|
||||
return walletData
|
||||
} catch (err) {
|
||||
console.error('Error in openWallet()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
This script is the same as sweep-wallet.js, but instead of sweeping the keys,
|
||||
it simply lists out the addresses in the HD keychain. This is hany when trying
|
||||
to figure out which address a token was sent to.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
|
||||
// Local libraries
|
||||
import WalletAdapter from '../../src/adapters/wallet.js'
|
||||
|
||||
// Constants
|
||||
const START_INDEX = 1
|
||||
const LAST_ADDR_INDEX = 100
|
||||
|
||||
async function listAddrs () {
|
||||
try {
|
||||
// Configure the wallet library to use a FullStack.cash or a local Cash Stack
|
||||
const walletLib = new WalletAdapter()
|
||||
|
||||
// Open the wallet files.
|
||||
const walletInfo = await openWallet(walletLib)
|
||||
const wallet = await walletLib.instanceWallet(walletInfo)
|
||||
console.log('walletInfo: ', walletInfo)
|
||||
|
||||
const rootAddr = walletInfo.cashAddress
|
||||
// const rootWif = walletInfo.privateKey
|
||||
console.log(`Sweeping all funds into root address ${rootAddr}...`)
|
||||
|
||||
// Generate an HD tree
|
||||
const bchjs = wallet.bchjs
|
||||
const rootSeed = await bchjs.Mnemonic.toSeed(walletInfo.mnemonic)
|
||||
const masterHDNode = bchjs.HDNode.fromSeed(rootSeed)
|
||||
|
||||
let hdIndex = START_INDEX
|
||||
|
||||
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(`HD Index ${hdIndex}: ${cashAddress}`)
|
||||
|
||||
hdIndex++
|
||||
} while (hdIndex < LAST_ADDR_INDEX)
|
||||
console.log(`Last index of ${LAST_ADDR_INDEX} reached.`)
|
||||
} catch (err) {
|
||||
console.error('Error in sweepFunds(): ', err)
|
||||
}
|
||||
}
|
||||
listAddrs()
|
||||
|
||||
// Open the wallet file, or create one if the file doesn't exist.
|
||||
// Does not instance the wallet. The output of this function is expected to
|
||||
// be passed to instanceWallet().
|
||||
async function openWallet (walletLib) {
|
||||
try {
|
||||
// console.log('this.WALLET_FILE: ', this.WALLET_FILE)
|
||||
const walletData = await walletLib.jsonFiles.readJSON('./wallet.json')
|
||||
|
||||
return walletData
|
||||
} catch (err) {
|
||||
console.error('Error in openWallet()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -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,16 +2,14 @@
|
||||
This script will travers the HD wallet and sweep funds and tokens back
|
||||
into the root address (index 0). That root address needs to have funds
|
||||
to pay for the transactions.
|
||||
|
||||
This script will sweep BCH and fungible (Type 1) tokens, but not NFTs.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
// const BCHJS = require('@psf/bch-js')
|
||||
const BchTokenSweep = require('bch-token-sweep/index')
|
||||
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
|
||||
|
||||
@@ -13,7 +13,7 @@ export COORD_NAME=ipfs-service-provider-generic
|
||||
export MNEMONIC="churn aisle shield silver ladder swear hunt slim pen demand spoil veteran"
|
||||
|
||||
# 0 = less verbose. 3 = most verbose
|
||||
export DEBUG_LEVEL=2
|
||||
export DEBUG_LEVEL=0
|
||||
|
||||
# Production settings that use external IPFS node.
|
||||
# https://github.com/christroutner/docker-ipfs
|
||||
|
||||
+18
-8
@@ -13,16 +13,24 @@
|
||||
in the Adapter directory.
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
const axios = require('axios').default
|
||||
const mongoose = require('mongoose')
|
||||
const User = require('../adapters/localdb/models/users')
|
||||
const config = require('../../config')
|
||||
const JsonFiles = require('../adapters/json-files')
|
||||
// Global npm libraries
|
||||
import axios from 'axios'
|
||||
import mongoose from 'mongoose'
|
||||
|
||||
// Local libraries
|
||||
import User from '../adapters/localdb/models/users.js'
|
||||
import config from '../../config/index.js'
|
||||
import JsonFiles from '../adapters/json-files.js'
|
||||
|
||||
// Hack to get __dirname back.
|
||||
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
|
||||
import * as url from 'url'
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||
|
||||
const jsonFiles = new JsonFiles()
|
||||
|
||||
const JSON_FILE = `system-user-${config.env}.json`
|
||||
const JSON_PATH = `${__dirname.toString()}/../../config/${JSON_FILE}`
|
||||
const JSON_PATH = `${__dirname.toString()}../../config/${JSON_FILE}`
|
||||
|
||||
const LOCALHOST = `http://localhost:${config.port}`
|
||||
const context = {}
|
||||
@@ -82,6 +90,8 @@ class Admin {
|
||||
// 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)
|
||||
|
||||
return context
|
||||
} catch (err) {
|
||||
@@ -169,4 +179,4 @@ class Admin {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Admin
|
||||
export default Admin
|
||||
|
||||
+5
-4
@@ -3,9 +3,10 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const BCHJS = require('@psf/bch-js')
|
||||
const MsgLib = require('bch-message-lib')
|
||||
const BchWallet = require('minimal-slp-wallet/index')
|
||||
import BCHJS from '@psf/bch-js'
|
||||
|
||||
import MsgLib from 'bch-message-lib'
|
||||
import BchWallet from 'minimal-slp-wallet'
|
||||
|
||||
class Bch {
|
||||
constructor () {
|
||||
@@ -79,4 +80,4 @@ class Bch {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Bch
|
||||
export default Bch
|
||||
|
||||
@@ -3,13 +3,12 @@
|
||||
*/
|
||||
|
||||
/* eslint-disable no-useless-escape */
|
||||
import config from '../../config/index.js'
|
||||
import NodeMailer from '../adapters/nodemailer.js'
|
||||
import wlogger from '../adapters/wlogger.js'
|
||||
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
|
||||
|
||||
const config = require('../../config')
|
||||
|
||||
const NodeMailer = require('../adapters/nodemailer')
|
||||
const nodemailer = new NodeMailer()
|
||||
const { wlogger } = require('../adapters/wlogger')
|
||||
|
||||
let _this
|
||||
|
||||
@@ -60,4 +59,4 @@ class ContactLib {
|
||||
}
|
||||
}
|
||||
}
|
||||
module.exports = ContactLib
|
||||
export default ContactLib
|
||||
|
||||
@@ -5,8 +5,9 @@
|
||||
for this file.
|
||||
*/
|
||||
|
||||
const JwtLib = require('jwt-bch-lib')
|
||||
const BCHJS = require('@psf/bch-js')
|
||||
import JwtLib from 'jwt-bch-lib'
|
||||
|
||||
import BCHJS from '@psf/bch-js'
|
||||
|
||||
class FullStackJWT {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -98,4 +99,4 @@ class FullStackJWT {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FullStackJWT
|
||||
export default FullStackJWT
|
||||
|
||||
+38
-16
@@ -5,23 +5,25 @@
|
||||
*/
|
||||
|
||||
// Public NPM libraries
|
||||
const BCHJS = require('@psf/bch-js')
|
||||
import BCHJS from '@psf/bch-js'
|
||||
|
||||
// Load individual adapter libraries.
|
||||
const IPFSAdapter = require('./ipfs')
|
||||
const LocalDB = require('./localdb')
|
||||
const LogsAPI = require('./logapi')
|
||||
const Passport = require('./passport')
|
||||
const Nodemailer = require('./nodemailer')
|
||||
import IPFSAdapter from './ipfs/index.js'
|
||||
import LocalDB from './localdb/index.js'
|
||||
import LogsAPI from './logapi.js'
|
||||
import Passport from './passport.js'
|
||||
import Nodemailer from './nodemailer.js'
|
||||
// const { wlogger } = require('./wlogger')
|
||||
const JSONFiles = require('./json-files')
|
||||
const FullStackJWT = require('./fullstack-jwt')
|
||||
const BCHAdapter = require('./bch')
|
||||
const WalletAdapter = require('./wallet')
|
||||
const P2wdbAdapter = require('./p2wdb-adapter')
|
||||
const Webhook = require('./webhook')
|
||||
import JSONFiles from './json-files.js'
|
||||
import FullStackJWT from './fullstack-jwt.js'
|
||||
import config from '../../config/index.js'
|
||||
// import Wallet from './wallet.adapter.js'
|
||||
|
||||
const config = require('../../config')
|
||||
import BCHAdapter from './bch.js'
|
||||
import WalletAdapter from './wallet.js'
|
||||
import P2wdbAdapter from './p2wdb-adapter.js'
|
||||
import Webhook from './webhook.js'
|
||||
import NostrAdapter from './nostr.js'
|
||||
|
||||
class Adapters {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -40,6 +42,9 @@ class Adapters {
|
||||
localConfig.bchWallet = this.wallet.bchWallet
|
||||
this.p2wdb = new P2wdbAdapter(localConfig)
|
||||
this.webhook = new Webhook()
|
||||
this.nostr = new NostrAdapter(this.config)
|
||||
|
||||
// this.wallet = new Wallet(localConfig)
|
||||
|
||||
// Get a valid JWT API key and instance bch-js.
|
||||
this.fullStackJwt = new FullStackJWT(config)
|
||||
@@ -47,14 +52,22 @@ class Adapters {
|
||||
|
||||
async start () {
|
||||
try {
|
||||
// let apiToken
|
||||
if (this.config.getJwtAtStartup) {
|
||||
// Get a JWT token and instantiate bch-js with it. Then pass that instance
|
||||
// to all the rest of the apps controllers and adapters.
|
||||
await this.fullStackJwt.getJWT()
|
||||
// apiToken = await this.fullStackJwt.getJWT()
|
||||
// Instantiate bch-js with the JWT token, and overwrite the placeholder for bch-js.
|
||||
this.bchjs = await this.fullStackJwt.instanceBchjs()
|
||||
}
|
||||
|
||||
// Create a default instance of minimal-slp-wallet without initializing it
|
||||
// (without retrieving the wallets UTXOs). This instance will be overwritten
|
||||
// if the operator has configured BCH payments.
|
||||
console.log('\nCreating default startup wallet. This wallet may be overwritten.')
|
||||
// await this.wallet.instanceWalletWithoutInitialization({}, { apiToken })
|
||||
this.bchjs = this.wallet.bchWallet.bchjs
|
||||
|
||||
// Start the IPFS node.
|
||||
// Do not start these adapters if this is an e2e test.
|
||||
// if (this.config.env !== 'test') {
|
||||
@@ -63,6 +76,8 @@ class Adapters {
|
||||
|
||||
// Open the wallet file
|
||||
const walletData = await this.wallet.openWallet()
|
||||
await this.nostr.start(walletData.privateKey)
|
||||
|
||||
// console.log('walletData: ', walletData)
|
||||
|
||||
if (this.config.env !== 'test') {
|
||||
@@ -70,11 +85,18 @@ class Adapters {
|
||||
await this.wallet.instanceWallet(walletData, this.bchjs)
|
||||
|
||||
// Wait until a webhook is established with the P2WDB
|
||||
await this.webhook.waitUntilSuccess(this.config.webhookTarget)
|
||||
// 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
|
||||
|
||||
if (this.config.useIpfs) {
|
||||
await this.ipfs.start()
|
||||
}
|
||||
} else {
|
||||
// These lines are here to ensure code coverage hits 100%.
|
||||
console.log('Not starting IPFS node since this is an e2e test.')
|
||||
}
|
||||
|
||||
console.log('Async Adapters have been started.')
|
||||
@@ -87,4 +109,4 @@ class Adapters {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Adapters
|
||||
export default Adapters
|
||||
|
||||
+139
-4
@@ -3,9 +3,10 @@
|
||||
*/
|
||||
|
||||
// Local libraries
|
||||
const IpfsAdapter = require('./ipfs')
|
||||
const IpfsCoordAdapter = require('./ipfs-coord')
|
||||
const config = require('../../../config')
|
||||
import IpfsAdapter from './ipfs.js'
|
||||
|
||||
import IpfsCoordAdapter from './ipfs-coord.js'
|
||||
import config from '../../../config/index.js'
|
||||
|
||||
class IPFS {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -57,6 +58,140 @@ class IPFS {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get the status of this IPFS node.
|
||||
// Create an object with different metrics about this IPFS node, then return
|
||||
// the object.
|
||||
getStatus () {
|
||||
try {
|
||||
const statusObj = {
|
||||
ipfsId: this.ipfsCoordAdapter.ipfsCoord.thisNode.ipfsId,
|
||||
multiAddrs: this.ipfsCoordAdapter.ipfsCoord.thisNode.ipfsMultiaddrs,
|
||||
bchAddr: this.ipfsCoordAdapter.ipfsCoord.thisNode.bchAddr,
|
||||
slpAddr: this.ipfsCoordAdapter.ipfsCoord.thisNode.slpAddr,
|
||||
pubKey: this.ipfsCoordAdapter.ipfsCoord.thisNode.pubKey,
|
||||
peers: this.ipfsCoordAdapter.ipfsCoord.thisNode.peerList.length,
|
||||
relays: this.ipfsCoordAdapter.ipfsCoord.thisNode.relayData.length
|
||||
}
|
||||
|
||||
return statusObj
|
||||
} catch (err) {
|
||||
console.error('Error in ipfs-coord.js/getStatus()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get details on the other peers this node is connected to.
|
||||
async getPeers (showAll) {
|
||||
try {
|
||||
const peerData = this.ipfsCoordAdapter.ipfsCoord.thisNode.peerData
|
||||
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
|
||||
|
||||
let ipfsPeers =
|
||||
await this.ipfsCoordAdapter.ipfsCoord.adapters.ipfs.getPeers()
|
||||
// console.log('ipfsPeers: ', ipfsPeers)
|
||||
|
||||
ipfsPeers = this._removeDuplicatePeers(ipfsPeers)
|
||||
// console.log('filtered ipfsPeers: ', ipfsPeers)
|
||||
|
||||
const peerDataOut = []
|
||||
|
||||
// Loop through each IPFS peer and hydrate it with data from the peerData.
|
||||
for (let i = 0; i < ipfsPeers.length; i++) {
|
||||
const thisPeer = {
|
||||
peer: ipfsPeers[i]
|
||||
}
|
||||
|
||||
// if (!showAll) {
|
||||
// // Delete properties that don't contain good info.
|
||||
// delete thisPeer.muxer
|
||||
// delete thisPeer.latency
|
||||
// delete thisPeer.streams
|
||||
// }
|
||||
|
||||
// Get the ipfs-coord peer data for this peer.
|
||||
let thisPeerData = peerData.filter((x) =>
|
||||
x.from.includes(thisPeer.peer)
|
||||
)
|
||||
thisPeerData = thisPeerData[0]
|
||||
// console.log('thisPeerData: ', thisPeerData)
|
||||
|
||||
// Skip if peerData for this IPFS peer could not be found.
|
||||
// if (!thisPeerData) continue
|
||||
|
||||
try {
|
||||
// console.log('thisPeerData: ', thisPeerData)
|
||||
|
||||
// Add data to the IPFS peer data.
|
||||
thisPeer.name = thisPeerData.data.jsonLd.name
|
||||
thisPeer.protocol = thisPeerData.data.jsonLd.protocol
|
||||
thisPeer.version = thisPeerData.data.jsonLd.version
|
||||
thisPeer.connectionAddr = thisPeerData.data.connectionAddr
|
||||
|
||||
if (showAll) {
|
||||
// Add all the peer data.
|
||||
thisPeer.peerData = thisPeerData
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
`warning: ${thisPeer.peer}: ${err.message}`
|
||||
)
|
||||
}
|
||||
|
||||
peerDataOut.push(thisPeer)
|
||||
}
|
||||
|
||||
return peerDataOut
|
||||
} catch (err) {
|
||||
console.error('Error in getPeers(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Expects an array of peers and returns an array of peers with duplicates
|
||||
// removed.
|
||||
// This function is used by getPeers()
|
||||
_removeDuplicatePeers (arr) {
|
||||
// https://stackoverflow.com/questions/2218999/how-to-remove-all-duplicates-from-an-array-of-objects
|
||||
return arr.filter((v, i, a) => a.findIndex((t) => t === v) === i)
|
||||
}
|
||||
|
||||
// Get data about the known Circuit Relays. Hydrate with data from peers list.
|
||||
getRelays () {
|
||||
try {
|
||||
const relayData = this.ipfsCoordAdapter.ipfsCoord.thisNode.relayData
|
||||
const peerData = this.ipfsCoordAdapter.ipfsCoord.thisNode.peerData
|
||||
// console.log(`relayData: ${JSON.stringify(relayData, null, 2)}`)
|
||||
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
|
||||
|
||||
for (let i = 0; i < relayData.length; i++) {
|
||||
const thisRelay = relayData[i]
|
||||
|
||||
// Find the peer that corresponds to this relay.
|
||||
const thisPeer = peerData.filter((x) =>
|
||||
x.from.includes(thisRelay.ipfsId)
|
||||
)
|
||||
// console.log('thisPeer: ', thisPeer)
|
||||
|
||||
// If the peer couldn't be found, skip.
|
||||
if (!thisPeer.length) {
|
||||
thisRelay.name = ''
|
||||
thisRelay.description = ''
|
||||
continue
|
||||
}
|
||||
|
||||
thisRelay.name = thisPeer[0].data.jsonLd.name
|
||||
thisRelay.description = thisPeer[0].data.jsonLd.description
|
||||
}
|
||||
|
||||
const v1Relays = this.config.v1Relays
|
||||
|
||||
return { v2Relays: relayData, v1Relays }
|
||||
} catch (err) {
|
||||
console.error('Error in getRelays(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = IPFS
|
||||
export default IPFS
|
||||
|
||||
@@ -5,12 +5,16 @@
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
const IpfsCoord = require('ipfs-coord')
|
||||
const BCHJS = require('@psf/bch-js')
|
||||
const publicIp = require('public-ip')
|
||||
// import IpfsCoord from 'ipfs-coord-esm'
|
||||
import IpfsCoord from 'helia-coord'
|
||||
|
||||
// import BCHJS from '@psf/bch-js';
|
||||
import SlpWallet from 'minimal-slp-wallet'
|
||||
import { publicIpv4 } from 'public-ip'
|
||||
|
||||
// Local libraries
|
||||
const config = require('../../../config')
|
||||
import config from '../../../config/index.js'
|
||||
|
||||
// const JSONRPC = require('../../controllers/json-rpc/')
|
||||
|
||||
let _this
|
||||
@@ -28,9 +32,10 @@ class IpfsCoordAdapter {
|
||||
// Encapsulate dependencies
|
||||
this.IpfsCoord = IpfsCoord
|
||||
this.ipfsCoord = {}
|
||||
this.bchjs = new BCHJS()
|
||||
// this.bchjs = new BCHJS()
|
||||
this.wallet = new SlpWallet()
|
||||
this.config = config
|
||||
this.publicIp = publicIp
|
||||
this.publicIp = publicIpv4
|
||||
|
||||
// Properties of this class instance.
|
||||
this.isReady = false
|
||||
@@ -41,10 +46,13 @@ class IpfsCoordAdapter {
|
||||
async start () {
|
||||
const circuitRelayInfo = {}
|
||||
|
||||
// Wait for the BCH wallet to create the wallet.
|
||||
await this.wallet.walletInfoPromise
|
||||
|
||||
// If configured as a Circuit Relay, get the public IP addresses for this node.
|
||||
if (this.config.isCircuitRelay) {
|
||||
try {
|
||||
const ip4 = await this.publicIp.v4()
|
||||
const ip4 = await this.publicIp()
|
||||
// const ip6 = await publicIp.v6()
|
||||
|
||||
circuitRelayInfo.ip4 = ip4
|
||||
@@ -57,23 +65,28 @@ class IpfsCoordAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
const nullLog = () => {}
|
||||
|
||||
const ipfsCoordOptions = {
|
||||
ipfs: this.ipfs,
|
||||
type: 'node.js',
|
||||
// type: 'browser',
|
||||
bchjs: this.bchjs,
|
||||
privateLog: console.log, // Default to console.log
|
||||
wallet: this.wallet,
|
||||
// privateLog: console.log, // Default to console.log
|
||||
privateLog: nullLog,
|
||||
isCircuitRelay: this.config.isCircuitRelay,
|
||||
circuitRelayInfo,
|
||||
apiInfo: this.config.apiInfo,
|
||||
announceJsonLd: this.config.announceJsonLd,
|
||||
debugLevel: this.config.debugLevel
|
||||
debugLevel: this.config.debugLevel,
|
||||
v1Relays: this.config.v1Relays,
|
||||
tcpPort: this.config.ipfsTcpPort
|
||||
}
|
||||
|
||||
// Production env uses external go-ipfs node.
|
||||
if (this.config.isProduction) {
|
||||
ipfsCoordOptions.nodeType = 'external'
|
||||
}
|
||||
// if (this.config.isProduction) {
|
||||
// ipfsCoordOptions.nodeType = 'external'
|
||||
// }
|
||||
|
||||
this.ipfsCoord = new this.IpfsCoord(ipfsCoordOptions)
|
||||
|
||||
@@ -100,12 +113,15 @@ class IpfsCoordAdapter {
|
||||
|
||||
// Subscribe to the chat pubsub channel
|
||||
async subscribeToChat () {
|
||||
// TODO: Allow user to replace nullog with their own log handler at startup.
|
||||
const nullLog = () => {}
|
||||
|
||||
await this.ipfsCoord.adapters.pubsub.subscribeToPubsubChannel(
|
||||
this.config.chatPubSubChan,
|
||||
console.log,
|
||||
nullLog,
|
||||
this.ipfsCoord.thisNode
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = IpfsCoordAdapter
|
||||
export default IpfsCoordAdapter
|
||||
|
||||
+227
-82
@@ -9,94 +9,95 @@
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
// const IPFS = require('ipfs')
|
||||
// const IPFS = require('@chris.troutner/ipfs')
|
||||
const IPFSembedded = require('ipfs')
|
||||
const IPFSexternal = require('ipfs-http-client')
|
||||
const fs = require('fs')
|
||||
const http = require('http')
|
||||
import { createHelia } from 'helia'
|
||||
import fs from 'fs'
|
||||
import { FsBlockstore } from 'blockstore-fs'
|
||||
import { FsDatastore } from 'datastore-fs'
|
||||
import { createLibp2p } from 'libp2p'
|
||||
import { tcp } from '@libp2p/tcp'
|
||||
import { noise } from '@chainsafe/libp2p-noise'
|
||||
import { yamux } from '@chainsafe/libp2p-yamux'
|
||||
// import { bootstrap } from '@libp2p/bootstrap'
|
||||
// import { identifyService } from 'libp2p/identify'
|
||||
import { identify } from '@libp2p/identify'
|
||||
// import { circuitRelayServer, circuitRelayTransport } from '@libp2p/circuit-relay-v2'
|
||||
import { circuitRelayServer } from '@libp2p/circuit-relay-v2'
|
||||
import { gossipsub } from '@chainsafe/libp2p-gossipsub'
|
||||
import { webSockets } from '@libp2p/websockets'
|
||||
import { publicIpv4 } from 'public-ip'
|
||||
import { multiaddr } from '@multiformats/multiaddr'
|
||||
// import { webRTC } from '@libp2p/webrtc'
|
||||
import { keychain } from '@libp2p/keychain'
|
||||
import { defaultLogger } from '@libp2p/logger'
|
||||
import { unixfs } from '@helia/unixfs'
|
||||
|
||||
// Local libraries
|
||||
const config = require('../../../config')
|
||||
import config from '../../../config/index.js'
|
||||
import JsonFiles from '../json-files.js'
|
||||
|
||||
const IPFS_DIR = './.ipfsdata/ipfs'
|
||||
// Hack to get __dirname back.
|
||||
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
|
||||
import * as url from 'url'
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||
// console.log('__dirname: ', __dirname)
|
||||
|
||||
const ROOT_DIR = `${__dirname}../../../`
|
||||
const IPFS_DIR = `${__dirname}../../../.ipfsdata/ipfs`
|
||||
|
||||
class IpfsAdapter {
|
||||
constructor (localConfig) {
|
||||
// Encapsulate dependencies
|
||||
this.config = config
|
||||
|
||||
// Choose the IPFS constructor based on the config settings.
|
||||
this.IPFS = IPFSembedded // default
|
||||
if (this.config.isProduction) {
|
||||
this.IPFS = IPFSexternal
|
||||
}
|
||||
this.fs = fs
|
||||
this.createLibp2p = createLibp2p
|
||||
this.publicIp = publicIpv4
|
||||
this.multiaddr = multiaddr
|
||||
this.jsonFiles = new JsonFiles()
|
||||
this.keychain = keychain
|
||||
this.createHelia = createHelia
|
||||
|
||||
// Properties of this class instance.
|
||||
this.isReady = false
|
||||
|
||||
this.fs = fs
|
||||
// Bind 'this' object to all subfunctions
|
||||
this.start = this.start.bind(this)
|
||||
this.createNode = this.createNode.bind(this)
|
||||
this.stop = this.stop.bind(this)
|
||||
this.ensureBlocksDir = this.ensureBlocksDir.bind(this)
|
||||
this.getSeed = this.getSeed.bind(this)
|
||||
this.getKeychain = this.getKeychain.bind(this)
|
||||
}
|
||||
|
||||
// Start an IPFS node.
|
||||
async start () {
|
||||
try {
|
||||
// Ipfs Options
|
||||
const ipfsOptionsEmbedded = {
|
||||
repo: IPFS_DIR,
|
||||
start: true,
|
||||
config: {
|
||||
relay: {
|
||||
enabled: true, // enable circuit relay dialer and listener
|
||||
hop: {
|
||||
enabled: config.isCircuitRelay // enable circuit relay HOP (make this node a relay)
|
||||
}
|
||||
},
|
||||
pubsub: true, // enable pubsub
|
||||
Swarm: {
|
||||
ConnMgr: {
|
||||
HighWater: 30,
|
||||
LowWater: 10
|
||||
}
|
||||
},
|
||||
Addresses: {
|
||||
Swarm: [
|
||||
`/ip4/0.0.0.0/tcp/${this.config.ipfsTcpPort}`,
|
||||
`/ip4/0.0.0.0/tcp/${this.config.ipfsWsPort}/ws`
|
||||
]
|
||||
},
|
||||
Datastore: {
|
||||
StorageMax: '2GB',
|
||||
StorageGCWatermark: 50,
|
||||
GCPeriod: '15m'
|
||||
}
|
||||
}
|
||||
}
|
||||
// Ensure the directory structure exists that is needed by the IPFS node to store data.
|
||||
this.ensureBlocksDir()
|
||||
|
||||
const ipfsOptionsExternal = {
|
||||
host: this.config.ipfsHost,
|
||||
port: this.config.ipfsApiPort,
|
||||
agent: http.Agent({ keepAlive: true, maxSockets: 2000 })
|
||||
}
|
||||
// Create an IPFS node
|
||||
const ipfs = await this.createNode()
|
||||
// console.log('ipfs: ', ipfs)
|
||||
|
||||
let ipfsOptions = ipfsOptionsEmbedded
|
||||
if (this.config.isProduction) {
|
||||
ipfsOptions = ipfsOptionsExternal
|
||||
}
|
||||
this.id = ipfs.libp2p.peerId.toString()
|
||||
console.log('IPFS ID: ', this.id)
|
||||
|
||||
// Create a new IPFS node.
|
||||
this.ipfs = await this.IPFS.create(ipfsOptions)
|
||||
// Attempt to guess our ip4 IP address.
|
||||
const ip4 = await this.publicIp()
|
||||
let detectedMultiaddr = `/ip4/${ip4}/tcp/${this.config.ipfsTcpPort}/p2p/${this.id}`
|
||||
detectedMultiaddr = this.multiaddr(detectedMultiaddr)
|
||||
|
||||
// Set the 'server' profile so the node does not scan private networks.
|
||||
await this.ipfs.config.profiles.apply('server')
|
||||
// Get the multiaddrs for the node.
|
||||
const multiaddrs = ipfs.libp2p.getMultiaddrs()
|
||||
multiaddrs.push(detectedMultiaddr)
|
||||
console.log('Multiaddrs: ', multiaddrs)
|
||||
|
||||
// Debugging: Display IPFS config settings.
|
||||
// const configSettings = await this.ipfs.config.getAll()
|
||||
// console.log(`configSettings: ${JSON.stringify(configSettings, null, 2)}`)
|
||||
this.multiaddrs = multiaddrs
|
||||
|
||||
// Signal that this adapter is ready.
|
||||
this.isReady = true
|
||||
|
||||
this.ipfs = ipfs
|
||||
|
||||
return this.ipfs
|
||||
} catch (err) {
|
||||
console.error('Error in ipfs.js/start()')
|
||||
@@ -110,31 +111,175 @@ class IpfsAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
async getKeychain (datastore) {
|
||||
const keychainInit = {
|
||||
pass: await this.getSeed()
|
||||
}
|
||||
|
||||
const chain = this.keychain(keychainInit)({
|
||||
datastore,
|
||||
logger: defaultLogger()
|
||||
})
|
||||
|
||||
return chain
|
||||
}
|
||||
|
||||
// This function creates an IPFS node using Helia.
|
||||
// It returns the node as an object.
|
||||
async createNode () {
|
||||
try {
|
||||
// Create block and data stores.
|
||||
const blockstore = new FsBlockstore(`${IPFS_DIR}/blockstore`)
|
||||
const datastore = new FsDatastore(`${IPFS_DIR}/datastore`)
|
||||
|
||||
// const keychainInit = {
|
||||
// pass: await this.getSeed()
|
||||
// }
|
||||
|
||||
// Create an identity
|
||||
let peerId
|
||||
// console.log('this.keychain: ', this.keychain)
|
||||
// const chain = this.keychain(keychainInit)({
|
||||
// datastore,
|
||||
// logger: defaultLogger()
|
||||
// })
|
||||
const chain = await this.getKeychain(datastore)
|
||||
try {
|
||||
peerId = await chain.exportPeerId('myKey')
|
||||
} catch (err) {
|
||||
await chain.createKey('myKey', 'Ed25519', 4096)
|
||||
peerId = await chain.exportPeerId('myKey')
|
||||
}
|
||||
|
||||
// Configure services
|
||||
const services = {
|
||||
identify: identify(),
|
||||
pubsub: gossipsub({ allowPublishToZeroTopicPeers: true })
|
||||
}
|
||||
if (this.config.isCircuitRelay) {
|
||||
console.log('Helia (IPFS) node IS configured as Circuit Relay')
|
||||
services.relay = circuitRelayServer({ // makes the node function as a relay server
|
||||
hopTimeout: 30 * 1000, // incoming relay requests must be resolved within this time limit
|
||||
advertise: true,
|
||||
reservations: {
|
||||
maxReservations: 15, // how many peers are allowed to reserve relay slots on this server
|
||||
reservationClearInterval: 300 * 1000, // how often to reclaim stale reservations
|
||||
applyDefaultLimit: true, // whether to apply default data/duration limits to each relayed connection
|
||||
defaultDurationLimit: 2 * 60 * 1000, // the default maximum amount of time a relayed connection can be open for
|
||||
defaultDataLimit: BigInt(2 << 7), // the default maximum number of bytes that can be transferred over a relayed connection
|
||||
maxInboundHopStreams: 32, // how many inbound HOP streams are allow simultaneously
|
||||
maxOutboundHopStreams: 64 // how many outbound HOP streams are allow simultaneously
|
||||
}
|
||||
})
|
||||
} else {
|
||||
console.log('Helia (IPFS) node IS NOT configured as Circuit Relay')
|
||||
}
|
||||
|
||||
const transports = [
|
||||
tcp(),
|
||||
webSockets()
|
||||
// circuitRelayTransport({
|
||||
// discoverRelays: 3,
|
||||
// reservationConcurrency: 3
|
||||
// }),
|
||||
// webRTC()
|
||||
]
|
||||
|
||||
// libp2p is the networking layer that underpins Helia
|
||||
const libp2p = await this.createLibp2p({
|
||||
peerId,
|
||||
datastore,
|
||||
addresses: {
|
||||
listen: [
|
||||
'/ip4/127.0.0.1/tcp/0',
|
||||
`/ip4/0.0.0.0/tcp/${this.config.ipfsTcpPort}`,
|
||||
`/ip4/0.0.0.0/tcp/${this.config.ipfsWsPort}/ws`
|
||||
// '/webrtc'
|
||||
]
|
||||
},
|
||||
transports,
|
||||
connectionEncryption: [
|
||||
noise()
|
||||
],
|
||||
streamMuxers: [
|
||||
yamux()
|
||||
],
|
||||
services
|
||||
})
|
||||
|
||||
// create a Helia node
|
||||
const helia = await this.createHelia({
|
||||
blockstore,
|
||||
datastore,
|
||||
libp2p
|
||||
})
|
||||
|
||||
// Attach IPFS file system.
|
||||
const fs = unixfs(helia)
|
||||
helia.fs = fs
|
||||
|
||||
return helia
|
||||
} catch (err) {
|
||||
console.error('Error creating Helia node: ', err)
|
||||
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async stop () {
|
||||
await this.ipfs.stop()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// Remove the '/blocks' directory that is used to store IPFS data.
|
||||
// Dev Note: It's assumed this node is not pinning any data and that
|
||||
// everything in this directory is transient. This folder will regularly
|
||||
// fill up and prevent IPFS from starting.
|
||||
// rmBlocksDir () {
|
||||
// try {
|
||||
// const dir = `${IPFS_DIR}/blocks`
|
||||
// console.log(`Deleting ${dir} directory...`)
|
||||
//
|
||||
// this.fs.rmdirSync(dir, { recursive: true })
|
||||
//
|
||||
// console.log(`${dir} directory is deleted!`)
|
||||
//
|
||||
// return true // Signal successful execution.
|
||||
// } catch (err) {
|
||||
// console.log('Error in rmBlocksDir()')
|
||||
// throw err
|
||||
// }
|
||||
// }
|
||||
// Ensure that the directories exist to store blocks from the IPFS network.
|
||||
// This function is called at startup, before the IPFS node is started.
|
||||
ensureBlocksDir () {
|
||||
try {
|
||||
!this.fs.existsSync(`${ROOT_DIR}.ipfsdata`) && this.fs.mkdirSync(`${ROOT_DIR}.ipfsdata`)
|
||||
|
||||
!this.fs.existsSync(`${IPFS_DIR}`) && this.fs.mkdirSync(`${IPFS_DIR}`)
|
||||
|
||||
!this.fs.existsSync(`${IPFS_DIR}/blockstore`) && this.fs.mkdirSync(`${IPFS_DIR}/blockstore`)
|
||||
|
||||
!this.fs.existsSync(`${IPFS_DIR}/datastore`) && this.fs.mkdirSync(`${IPFS_DIR}/datastore`)
|
||||
|
||||
// !fs.existsSync(`${IPFS_DIR}/datastore/peers`) && fs.mkdirSync(`${IPFS_DIR}/datastore/peers`)
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in adapters/ipfs.js/ensureBlocksDir(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// This function opens the seed used to generate the key for this IPFS peer.
|
||||
// The seed is stored in a JSON file. If it doesn't exist, a new one is created.
|
||||
async getSeed () {
|
||||
try {
|
||||
let seed
|
||||
|
||||
const filename = `${IPFS_DIR}/seed.json`
|
||||
|
||||
try {
|
||||
// Try to read the JSON file containing the seed.
|
||||
seed = await this.jsonFiles.readJSON(filename)
|
||||
} catch (err) {
|
||||
const seedNum = Math.floor(Math.random() * 1000000000000000000000)
|
||||
seed = seedNum.toString()
|
||||
|
||||
// Save the newly generated seed
|
||||
await this.jsonFiles.writeJSON(seed, filename)
|
||||
}
|
||||
|
||||
// console.log('getSeed() seed: ', seed)
|
||||
|
||||
return seed
|
||||
} catch (err) {
|
||||
console.error('Error in adapters/ipfs/ipfs.js/getSeed()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = IpfsAdapter
|
||||
export default IpfsAdapter
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
/*
|
||||
A utility file for reading and writing JSON files.
|
||||
*/
|
||||
'use strict'
|
||||
const fs = require('fs')
|
||||
|
||||
import fs from 'fs'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -75,4 +75,4 @@ class JsonFiles {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = JsonFiles
|
||||
export default JsonFiles
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
*/
|
||||
|
||||
// Load Mongoose models.
|
||||
const Users = require('./models/users')
|
||||
const Entries = require('./models/entry')
|
||||
const Order = require('./models/order')
|
||||
const Offer = require('./models/offer')
|
||||
import Users from './models/users.js'
|
||||
import Entries from './models/entry.js'
|
||||
import Order from './models/order.js'
|
||||
import Offer from './models/offer.js'
|
||||
|
||||
class LocalDB {
|
||||
constructor () {
|
||||
@@ -18,4 +18,4 @@ class LocalDB {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LocalDB
|
||||
export default LocalDB
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const mongoose = require('mongoose')
|
||||
import mongoose from 'mongoose'
|
||||
|
||||
const Entry = new mongoose.Schema({
|
||||
entry: { type: String },
|
||||
@@ -10,4 +10,4 @@ const Entry = new mongoose.Schema({
|
||||
merit: { type: Number }
|
||||
})
|
||||
|
||||
module.exports = mongoose.model('entry', Entry)
|
||||
export default mongoose.model('entry', Entry)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const mongoose = require('mongoose')
|
||||
import mongoose from 'mongoose'
|
||||
|
||||
const Offer = new mongoose.Schema({
|
||||
|
||||
@@ -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
|
||||
@@ -34,8 +34,9 @@ const Offer = new mongoose.Schema({
|
||||
// SWaP Protocol Properties
|
||||
lokadId: { type: String },
|
||||
messageType: { type: Number },
|
||||
messageClass: { type: Number }
|
||||
messageClass: { type: Number },
|
||||
nostrEventId: { type: String } // Nostr Event Id.
|
||||
|
||||
})
|
||||
|
||||
module.exports = mongoose.model('offer', Offer)
|
||||
export default mongoose.model('offer', Offer)
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
See the dev-docs/specification.md for details on each property.
|
||||
*/
|
||||
|
||||
const mongoose = require('mongoose')
|
||||
import mongoose from 'mongoose'
|
||||
|
||||
const Order = new mongoose.Schema({
|
||||
// Token data
|
||||
@@ -37,7 +37,8 @@ const Order = new mongoose.Schema({
|
||||
// SWaP Protocol Properties
|
||||
lokadId: { type: String },
|
||||
messageType: { type: Number },
|
||||
messageClass: { type: Number }
|
||||
messageClass: { type: Number },
|
||||
nostrEventId: { type: String } // Nostr Event Id.
|
||||
})
|
||||
|
||||
module.exports = mongoose.model('order', Order)
|
||||
export default mongoose.model('order', Order)
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
const mongoose = require('mongoose')
|
||||
const bcrypt = require('bcryptjs')
|
||||
const config = require('../../../../config')
|
||||
const jwt = require('jsonwebtoken')
|
||||
// Global npm libraries
|
||||
import mongoose from 'mongoose'
|
||||
import bcrypt from 'bcryptjs'
|
||||
import jwt from 'jsonwebtoken'
|
||||
|
||||
// Local libraries
|
||||
import config from '../../../../config/index.js'
|
||||
|
||||
const User = new mongoose.Schema({
|
||||
type: { type: String, default: 'user' },
|
||||
@@ -51,4 +54,4 @@ User.methods.generateToken = function generateToken () {
|
||||
}
|
||||
|
||||
// export default mongoose.model('user', User)
|
||||
module.exports = mongoose.model('user', User)
|
||||
export default mongoose.model('user', User)
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
orders from the datatabase.
|
||||
*/
|
||||
|
||||
const Order = require('./models/order')
|
||||
import Order from './models/order.js'
|
||||
|
||||
// let _this
|
||||
|
||||
@@ -39,4 +39,4 @@ class OrderPagination {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OrderPagination
|
||||
export default OrderPagination
|
||||
|
||||
@@ -1,9 +1,14 @@
|
||||
const lineReader = require('line-reader')
|
||||
const fs = require('fs')
|
||||
import lineReader from 'line-reader'
|
||||
import fs from 'fs'
|
||||
|
||||
const config = require('../../config')
|
||||
import config from '../../config/index.js'
|
||||
|
||||
// Hack to get __dirname back.
|
||||
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
|
||||
import * as url from 'url'
|
||||
|
||||
let _this
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||
|
||||
class LogsApi {
|
||||
constructor () {
|
||||
@@ -169,4 +174,4 @@ class LogsApi {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LogsApi
|
||||
export default LogsApi
|
||||
|
||||
@@ -3,11 +3,10 @@
|
||||
*/
|
||||
|
||||
'use strict'
|
||||
const nodemailer = require('nodemailer')
|
||||
import nodemailer from 'nodemailer'
|
||||
|
||||
const config = require('../../config')
|
||||
|
||||
const { wlogger } = require('./wlogger')
|
||||
import config from '../../config/index.js'
|
||||
import wlogger from './wlogger.js'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -150,4 +149,4 @@ class NodeMailer {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = NodeMailer
|
||||
export default NodeMailer
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
/*
|
||||
Adapter library for working with Nostr.
|
||||
*/
|
||||
|
||||
import BchNostr from 'bch-nostr'
|
||||
import { RelayPool } from 'nostr'
|
||||
|
||||
class NostrAdapter {
|
||||
constructor (localConfig = { nostrRelay: '', nostrTopic: '' }) {
|
||||
this.relayWs = localConfig.nostrRelay //
|
||||
this.topic = localConfig.nostrTopic
|
||||
|
||||
if (!this.relayWs) {
|
||||
throw new Error(
|
||||
'"nostrRelay" must be passed when instantiate NostrAdapter'
|
||||
)
|
||||
}
|
||||
|
||||
if (!this.topic) {
|
||||
throw new Error(
|
||||
'"nostrTopic" must be passed when instantiate NostrAdapter'
|
||||
)
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.bchNostr = new BchNostr()
|
||||
this.RelayPool = RelayPool
|
||||
|
||||
// Bind the 'this' object
|
||||
this.start = this.start.bind(this)
|
||||
this.post = this.post.bind(this)
|
||||
this.read = this.read.bind(this)
|
||||
}
|
||||
|
||||
// Create nostr keys.
|
||||
// NOTE: WIF must be provided in this function instead the constructor function
|
||||
// we can provide it after app wallet creation.
|
||||
async start (WIF) {
|
||||
try {
|
||||
if (!WIF || typeof WIF !== 'string') {
|
||||
throw new Error('WIF must be a string!')
|
||||
}
|
||||
|
||||
const { privKeyBuf, nostrPubKey } =
|
||||
this.bchNostr.keys.createNostrPubKeyFromWif({ wif: WIF })
|
||||
|
||||
this.privKeyBuf = privKeyBuf
|
||||
this.nostrPubKey = nostrPubKey
|
||||
|
||||
console.log(`Nostr Pub Key : ${nostrPubKey}`)
|
||||
|
||||
return true
|
||||
} catch (error) {
|
||||
console.log(`Error in nostr.js/start() ${error.message} `)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Post a message over the instance-topic.
|
||||
async post (msg = '') {
|
||||
try {
|
||||
if (!msg || typeof msg !== 'string') {
|
||||
throw new Error('msg must be a string!')
|
||||
}
|
||||
|
||||
const inObj = {
|
||||
kind: 867,
|
||||
privKeyBuf: this.privKeyBuf,
|
||||
nostrPubKey: this.nostrPubKey,
|
||||
relayWs: this.relayWs,
|
||||
msg,
|
||||
tags: [['t', this.topic]]
|
||||
}
|
||||
const eventId = await this.bchNostr.post.uploadToNostr(inObj)
|
||||
return eventId
|
||||
} catch (error) {
|
||||
console.log(`Error in nostr.js/post() ${error.message} `)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Read a message over the instance-topic.
|
||||
async read (limit = 10) {
|
||||
try {
|
||||
if (typeof limit !== 'number' || limit < 1) {
|
||||
throw new Error('Limit must be greater than 0')
|
||||
}
|
||||
|
||||
const relays = [this.relayWs]
|
||||
const pool = this.RelayPool(relays)
|
||||
|
||||
const nostrData = new Promise((resolve, reject) => {
|
||||
const messages = []
|
||||
|
||||
pool.on('open', (relay) => {
|
||||
// relay.subscribe('REQ', { ids: [eventId] })
|
||||
relay.subscribe('REQ', { limit, kinds: [867], '#t': [this.topic] })
|
||||
})
|
||||
|
||||
pool.on('eose', (relay) => {
|
||||
relay.close()
|
||||
resolve(messages)
|
||||
})
|
||||
|
||||
pool.on('event', (relay, subId, ev) => {
|
||||
messages.push(ev.content)
|
||||
})
|
||||
})
|
||||
|
||||
const messages = await nostrData
|
||||
|
||||
return messages
|
||||
} catch (error) {
|
||||
console.log(`Error in nostr.js/read() ${error.message} `)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default NostrAdapter
|
||||
@@ -3,11 +3,13 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const axios = require('axios')
|
||||
const { Write, Read } = require('p2wdb/index')
|
||||
import axios from 'axios'
|
||||
|
||||
import P2WDB from 'p2wdb-esm'
|
||||
|
||||
// Local libraries
|
||||
const config = require('../../config')
|
||||
import config from '../../config/index.js'
|
||||
const { Write, Read } = P2WDB
|
||||
|
||||
class P2wdbAdapter {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -26,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
|
||||
@@ -111,4 +117,4 @@ class P2wdbAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = P2wdbAdapter
|
||||
export default P2wdbAdapter
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
koa-passport is an authorization library used for different authentication schemes.
|
||||
*/
|
||||
|
||||
const passport = require('koa-passport')
|
||||
import passport from 'koa-passport'
|
||||
|
||||
let _this
|
||||
class Passport {
|
||||
@@ -32,4 +32,4 @@ class Passport {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Passport
|
||||
export default Passport
|
||||
|
||||
@@ -0,0 +1,244 @@
|
||||
/*
|
||||
Wallet adapter. This library instantiates minimal-slp-wallet based on different
|
||||
configuration settings. That single instance of minimal-slp-wallet is then
|
||||
easily passed around the rest of this application.
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
import BchWallet from 'minimal-slp-wallet'
|
||||
|
||||
// Local libraries
|
||||
import config from '../../config/index.js'
|
||||
import JsonFiles from './json-files.js'
|
||||
|
||||
class Wallet {
|
||||
constructor (localConfig = {}) {
|
||||
// Encapsulate dependencies
|
||||
this.config = config
|
||||
this.BchWallet = BchWallet
|
||||
this.jsonFiles = new JsonFiles()
|
||||
|
||||
// Bind 'this' object to all subfunctions
|
||||
this.instanceWalletWithoutInitialization = this.instanceWalletWithoutInitialization.bind(this)
|
||||
}
|
||||
|
||||
// This is used for initializing the wallet, without waiting to update the wallet
|
||||
// UTXOs from the blockchain.
|
||||
// This is useful when the wallet is simply needed to make calls to the blockchain,
|
||||
// and there is no need to hydrate it with UTXO data.
|
||||
async instanceWalletWithoutInitialization (walletData = {}, advancedConfig = {}) {
|
||||
try {
|
||||
// Use the apiToken from the config settings, if one is not passed-in at
|
||||
// run-time.
|
||||
if (!advancedConfig.apiToken) {
|
||||
advancedConfig.apiToken = this.config.apiToken
|
||||
}
|
||||
|
||||
// If an authentication password is passed in the config, add it.
|
||||
if (this.config.authPass) {
|
||||
advancedConfig.authPass = this.config.authPass
|
||||
}
|
||||
|
||||
// Detect and configure different blockchain infrastructure settings.
|
||||
if (this.config.walletInterface === 'web2') {
|
||||
advancedConfig.interface = 'rest-api'
|
||||
advancedConfig.restURL = this.config.apiServer
|
||||
} else {
|
||||
// By default use the web3 Cash Stack (https://cashstack.info)
|
||||
advancedConfig.interface = 'consumer-api'
|
||||
advancedConfig.restURL = this.config.apiServer
|
||||
}
|
||||
|
||||
// console.log('advancedConfig setting when creating wallet: ', advancedConfig)
|
||||
|
||||
// Instantiate minimal-slp-wallet.
|
||||
if (walletData.mnemonic) {
|
||||
// Instance the wallet using the mnemonic passed in to this function.
|
||||
console.log('Mnemonic provided to wallet library from calling function.')
|
||||
this.bchWallet = await this._instanceWallet(walletData.mnemonic, advancedConfig)
|
||||
} else if (this.config.mnemonic) {
|
||||
// Otherwise use the mnemonic in the config setting or passed as an environment variable.
|
||||
console.log('Mnemonic retrieved from config file or environment variable.')
|
||||
this.bchWallet = await this._instanceWallet(this.config.mnemonic, advancedConfig)
|
||||
} else {
|
||||
// If no mnemonic is provided, then generate a new mnemonic to create the wallet.
|
||||
console.log('New mnemonic generated.')
|
||||
this.bchWallet = await this._instanceWallet(undefined, advancedConfig)
|
||||
}
|
||||
|
||||
// Wait for wallet to initialize.
|
||||
await this.bchWallet.walletInfoPromise
|
||||
console.log('BCH wallet initialized.')
|
||||
console.log(`Wallet address: ${this.bchWallet.walletInfo.cashAddress}`)
|
||||
console.log(`Wallet mnemonic: ${this.bchWallet.walletInfo.mnemonic}`)
|
||||
|
||||
return this.bchWallet
|
||||
} catch (err) {
|
||||
console.error('Error in instanceWalletWithoutInitialization()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// This function is used for easier mocking of unit tests.
|
||||
async _instanceWallet (mnemonic, config) {
|
||||
const wallet = new this.BchWallet(mnemonic, config)
|
||||
await wallet.walletInfoPromise
|
||||
|
||||
return wallet
|
||||
}
|
||||
|
||||
// Open the wallet file, or create one if the file doesn't exist.
|
||||
// Does not instance the wallet. The output of this function is expected to
|
||||
// be passed to instanceWallet().
|
||||
async openWallet (advancedConfig = {}) {
|
||||
try {
|
||||
let walletData
|
||||
|
||||
// Try to open the wallet.json file.
|
||||
try {
|
||||
walletData = await this.jsonFiles.readJSON(this.config.walletFile)
|
||||
walletData = walletData.wallet
|
||||
} catch (err) {
|
||||
// Create a new wallet file if one does not already exist.
|
||||
console.log('Wallet file not found. Creating new wallet.json file.')
|
||||
|
||||
// Create a new wallet.
|
||||
// No-Update flag creates wallet without making any network calls.
|
||||
const walletInstance = await this.instanceWalletWithoutInitialization({}, advancedConfig)
|
||||
|
||||
walletData = walletInstance.walletInfo
|
||||
|
||||
// Add the nextAddress property
|
||||
walletData.nextAddress = 1
|
||||
|
||||
// Write the wallet data to the JSON file.
|
||||
await this.jsonFiles.writeJSON({ wallet: walletData }, this.config.walletFile)
|
||||
}
|
||||
|
||||
// console.log('walletData: ', walletData)
|
||||
return walletData
|
||||
} catch (err) {
|
||||
console.error('Error in openWallet()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Create an instance of minimal-slp-wallet. Same as
|
||||
// instanceWalletWithoutInitialization(), but waits for the wallet to initialize
|
||||
// its UTXOs (wallet balance and tokens).
|
||||
async instanceWallet (walletData = {}, advancedConfig = {}) {
|
||||
try {
|
||||
// Instance the wallet without initialization.
|
||||
await this.instanceWalletWithoutInitialization(walletData, advancedConfig)
|
||||
|
||||
// Initialize the wallet
|
||||
await this.bchWallet.initialize()
|
||||
|
||||
return this.bchWallet
|
||||
} catch (err) {
|
||||
console.error('Error in wallet.js/instanceWallet()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Increments the 'nextAddress' property in the wallet file. This property
|
||||
// indicates the HD index that should be used to generate a key pair for
|
||||
// storing funds for Offers.
|
||||
// This function opens the wallet file, increments the nextAddress property,
|
||||
// then saves the change to the wallet file.
|
||||
async incrementNextAddress () {
|
||||
try {
|
||||
const walletData = await this.openWallet()
|
||||
|
||||
await this.instanceWalletWithoutInitialization(walletData)
|
||||
|
||||
walletData.nextAddress++
|
||||
// console.log('walletData finish: ', walletData)
|
||||
|
||||
await this.jsonFiles.writeJSON({ wallet: walletData }, this.config.walletFile)
|
||||
|
||||
// Update the working instance of the wallet.
|
||||
this.bchWallet.walletInfo.nextAddress++
|
||||
// console.log('this.bchWallet.walletInfo: ', this.bchWallet.walletInfo)
|
||||
|
||||
return walletData.nextAddress
|
||||
} catch (err) {
|
||||
console.error('Error in incrementNextAddress()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// 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) {
|
||||
// Increment the HD index and generate a new key pair.
|
||||
hdIndex = await this.incrementNextAddress()
|
||||
}
|
||||
|
||||
const mnemonic = this.bchWallet.walletInfo.mnemonic
|
||||
|
||||
// root seed buffer
|
||||
const rootSeed = await this.bchWallet.bchjs.Mnemonic.toSeed(mnemonic)
|
||||
const masterHDNode = this.bchWallet.bchjs.HDNode.fromSeed(rootSeed)
|
||||
|
||||
// HDNode of BIP44 account
|
||||
// const account = this.bchWallet.bchjs.HDNode.derivePath(masterHDNode, "m/44'/245'/0'")
|
||||
const childNode = masterHDNode.derivePath(`m/44'/245'/0'/0/${hdIndex}`)
|
||||
const cashAddress = this.bchWallet.bchjs.HDNode.toCashAddress(childNode)
|
||||
console.log('Generating a new key pair for cashAddress: ', cashAddress)
|
||||
|
||||
const wif = this.bchWallet.bchjs.HDNode.toWIF(childNode)
|
||||
|
||||
const outObj = {
|
||||
cashAddress,
|
||||
wif,
|
||||
hdIndex
|
||||
}
|
||||
return outObj
|
||||
} catch (err) {
|
||||
console.error('Error in getKeyPair()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Optimize the wallet by consolidating the UTXOs.
|
||||
async optimize () {
|
||||
const UTXO_THREASHOLD = 7
|
||||
// Do a dry-run first to see if there are enough UTXOs worth consolidating.
|
||||
const dryRunOut = await this.bchWallet.optimize(true)
|
||||
if (dryRunOut.bchUtxoCnt > UTXO_THREASHOLD) {
|
||||
// Consolidate BCH UTXOs if the count is above the threashold.
|
||||
const txids = await this.bchWallet.optimize()
|
||||
console.log(`Wallet optimized with these return values: ${JSON.stringify(txids, null, 2)}`)
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// Get the balance of the wallet in sats and PSF tokens.
|
||||
// This function is called by the GET /entry/balance controller.
|
||||
async getBalance () {
|
||||
const balance = await this.bchWallet.getBalance()
|
||||
// console.log('balance: ', balance)
|
||||
const tokens = await this.bchWallet.listTokens()
|
||||
// console.log('tokens: ', tokens)
|
||||
// Find the array entry for the PSF token
|
||||
const psfTokens = tokens.find(x => x.tokenId === '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0')
|
||||
// console.log('psfTokens: ', psfTokens)
|
||||
let psfBalance = 0
|
||||
if (psfTokens) {
|
||||
psfBalance = psfTokens.qty
|
||||
}
|
||||
const outObj = {
|
||||
satBalance: balance,
|
||||
psfBalance,
|
||||
success: true
|
||||
}
|
||||
return outObj
|
||||
}
|
||||
}
|
||||
|
||||
export default Wallet
|
||||
+63
-7
@@ -3,18 +3,28 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const BchWallet = require('minimal-slp-wallet/index')
|
||||
const bitcoinJs = require('bitcoincashjs-lib')
|
||||
import BchWallet from 'minimal-slp-wallet'
|
||||
import BchTokenSweep from 'bch-token-sweep/index.js'
|
||||
|
||||
import bitcoinJs from 'bitcoincashjs-lib'
|
||||
|
||||
// Local libraries
|
||||
const JsonFiles = require('./json-files')
|
||||
const config = require('../../config')
|
||||
import JsonFiles from './json-files.js'
|
||||
|
||||
import config from '../../config/index.js'
|
||||
|
||||
// Hack to get __dirname back.
|
||||
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
|
||||
import * as url from 'url'
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||
|
||||
const WALLET_FILE = `${__dirname.toString()}/../../wallet.json`
|
||||
// const PROOF_OF_BURN_QTY = 0.01
|
||||
// const P2WDB_TOKEN_ID =
|
||||
// '38e97c5d7d3585a2cbf3f9580c82ca33985f9cb0845d4dcce220cb709f9538b0'
|
||||
|
||||
const advancedConfig = {}
|
||||
|
||||
class WalletAdapter {
|
||||
constructor (localConfig = {}) {
|
||||
// Encapsulate dependencies
|
||||
@@ -24,6 +34,10 @@ class WalletAdapter {
|
||||
this.bitcoinJs = bitcoinJs
|
||||
this.BchWallet = BchWallet
|
||||
this.bchWallet = {} // Will be replaced when initialized.
|
||||
// this.advancedConfig = localConfig.advancedConfig
|
||||
|
||||
// Bind the 'this' object
|
||||
this.moveTokens = this.moveTokens.bind(this)
|
||||
}
|
||||
|
||||
// Open the wallet file, or create one if the file doesn't exist.
|
||||
@@ -77,7 +91,8 @@ class WalletAdapter {
|
||||
throw new Error('Wallet data is not formatted correctly. Can not read mnemonic in wallet file!')
|
||||
}
|
||||
|
||||
const advancedConfig = {}
|
||||
// const advancedConfig = {}
|
||||
console.log(`Using FullStack.cash: ${this.config.useFullStackCash}`)
|
||||
if (this.config.useFullStackCash) {
|
||||
advancedConfig.interface = 'rest-api'
|
||||
advancedConfig.restURL = this.config.apiServer
|
||||
@@ -349,7 +364,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.
|
||||
@@ -501,6 +516,47 @@ class WalletAdapter {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Given an Order database object, this function sends the tokens from the
|
||||
// HD index where they are stored, back to the root address of the wallet.
|
||||
async reclaimTokens (orderData) {
|
||||
try {
|
||||
// Get the key pair from the Order.
|
||||
const keyPair = await this.getKeyPair(orderData.hdIndex)
|
||||
console.log('keyPair: ', keyPair)
|
||||
|
||||
// Instantiate the wallet using the private key of the Order.
|
||||
// const tempWallet = new this.BchWallet(keyPair.wif, advancedConfig)
|
||||
// await tempWallet.initialize()
|
||||
|
||||
// Send the tokens from the Order to the root address.
|
||||
// const receiver = {
|
||||
// address: this.bchWallet.walletInfo.address,
|
||||
// tokenId: orderData.tokenId,
|
||||
// qty: orderData.numTokens
|
||||
// }
|
||||
// console.log('receiver: ', receiver)
|
||||
//
|
||||
// const txid = await tempWallet.sendTokens(receiver)
|
||||
|
||||
// Sweep the tokens to the root address.
|
||||
const sweeper = new BchTokenSweep(
|
||||
keyPair.wif,
|
||||
this.bchWallet.walletInfo.privateKey,
|
||||
this.bchWallet,
|
||||
550,
|
||||
this.bchWallet.walletInfo.cashAddress
|
||||
)
|
||||
await sweeper.populateObjectFromNetwork()
|
||||
const hex = await sweeper.sweepTo(this.bchWallet.walletInfo.cashAddress)
|
||||
const txid = await this.bchWallet.ar.sendTx(hex)
|
||||
|
||||
return txid
|
||||
} catch (err) {
|
||||
console.error('Error in wallet.js/reclaimTokens()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = WalletAdapter
|
||||
export default WalletAdapter
|
||||
|
||||
@@ -2,8 +2,9 @@
|
||||
The (Clean Architecture) Adapter for manageing a webhook connection to P2WDB.
|
||||
*/
|
||||
|
||||
const config = require('../../config')
|
||||
const axios = require('axios')
|
||||
import config from '../../config/index.js'
|
||||
|
||||
import axios from 'axios'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -101,4 +102,4 @@ function sleep (ms) {
|
||||
return new Promise(resolve => setTimeout(resolve, ms))
|
||||
}
|
||||
|
||||
module.exports = WebHook
|
||||
export default WebHook
|
||||
|
||||
+12
-4
@@ -6,10 +6,17 @@
|
||||
|
||||
'use strict'
|
||||
|
||||
const winston = require('winston')
|
||||
require('winston-daily-rotate-file')
|
||||
// Global npm libraries
|
||||
import winston from 'winston'
|
||||
import 'winston-daily-rotate-file'
|
||||
|
||||
const config = require('../../config')
|
||||
// Local libraries
|
||||
import config from '../../config/index.js'
|
||||
|
||||
// Hack to get __dirname back.
|
||||
// https://blog.logrocket.com/alternatives-dirname-node-js-es-modules/
|
||||
import * as url from 'url'
|
||||
const __dirname = url.fileURLToPath(new URL('.', import.meta.url))
|
||||
|
||||
class Wlogger {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -70,4 +77,5 @@ logger.outputToConsole()
|
||||
|
||||
const wlogger = logger.wlogger
|
||||
|
||||
module.exports = { wlogger, Wlogger }
|
||||
export { wlogger as default, Wlogger }
|
||||
// export default wlogger
|
||||
|
||||
@@ -7,18 +7,20 @@
|
||||
// Public npm libraries.
|
||||
|
||||
// Local libraries
|
||||
const Adapters = require('../adapters')
|
||||
const JSONRPC = require('./json-rpc')
|
||||
const UseCases = require('../use-cases')
|
||||
const RESTControllers = require('./rest-api')
|
||||
const TimerControllers = require('./timer-controllers')
|
||||
import Adapters from '../adapters/index.js'
|
||||
import JSONRPC from './json-rpc/index.js'
|
||||
import UseCases from '../use-cases/index.js'
|
||||
import RESTControllers from './rest-api/index.js'
|
||||
import TimerControllers from './timer-controllers.js'
|
||||
import config from '../../config/index.js'
|
||||
|
||||
class Controllers {
|
||||
constructor (localConfig = {}) {
|
||||
// Encapsulate dependencies
|
||||
this.adapters = new Adapters()
|
||||
this.useCases = new UseCases({ adapters: this.adapters })
|
||||
|
||||
this.timerControllers = new TimerControllers({ adapters: this.adapters, useCases: this.useCases })
|
||||
this.config = config
|
||||
}
|
||||
|
||||
// Spin up any adapter libraries that have async startup needs.
|
||||
@@ -45,8 +47,13 @@ class Controllers {
|
||||
|
||||
// Attach any other controllers other than REST API controllers.
|
||||
async attachControllers (app) {
|
||||
if (this.config.useIpfs) {
|
||||
// Attach JSON RPC controllers
|
||||
this.attachRPCControllers()
|
||||
}
|
||||
|
||||
// this.attachRPCControllers()
|
||||
// Attach and start the timer controllers
|
||||
this.timerControllers.startTimers()
|
||||
}
|
||||
|
||||
// Add the JSON RPC router to the ipfs-coord adapter.
|
||||
@@ -63,4 +70,4 @@ class Controllers {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Controllers
|
||||
export default Controllers
|
||||
|
||||
@@ -6,10 +6,10 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const jsonrpc = require('jsonrpc-lite')
|
||||
import jsonrpc from 'jsonrpc-lite'
|
||||
|
||||
// Local libraries
|
||||
const config = require('../../../../config')
|
||||
import config from '../../../../config/index.js'
|
||||
|
||||
class AboutRPC {
|
||||
constructor (localConfig) {
|
||||
@@ -48,4 +48,4 @@ class AboutRPC {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AboutRPC
|
||||
export default AboutRPC
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const jsonrpc = require('jsonrpc-lite')
|
||||
import jsonrpc from 'jsonrpc-lite'
|
||||
|
||||
// Local libraries
|
||||
// const AuthLib = require('../../lib/auth')
|
||||
// const UserLib = require('../../../use-cases/user')
|
||||
const { wlogger } = require('../../../adapters/wlogger')
|
||||
const RateLimit = require('../rate-limit')
|
||||
import wlogger from '../../../adapters/wlogger.js'
|
||||
|
||||
import RateLimit from '../rate-limit.js'
|
||||
|
||||
class AuthRPC {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -173,4 +174,4 @@ class AuthRPC {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AuthRPC
|
||||
export default AuthRPC
|
||||
|
||||
@@ -3,13 +3,14 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const jsonrpc = require('jsonrpc-lite')
|
||||
import jsonrpc from 'jsonrpc-lite'
|
||||
|
||||
// Local support libraries
|
||||
const { wlogger } = require('../../adapters/wlogger')
|
||||
const UserController = require('./users')
|
||||
const AuthController = require('./auth')
|
||||
const AboutController = require('./about')
|
||||
import wlogger from '../../adapters/wlogger.js'
|
||||
|
||||
import UserController from './users/index.js'
|
||||
import AuthController from './auth/index.js'
|
||||
import AboutController from './about/index.js'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -190,4 +191,4 @@ class JSONRPC {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = JSONRPC
|
||||
export default JSONRPC
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
/* eslint no-useless-catch: 0 */
|
||||
|
||||
// Local libraries
|
||||
const RateLimitLib = require('koa2-ratelimit').RateLimit
|
||||
import { RateLimit as RateLimitLib } from 'koa2-ratelimit'
|
||||
|
||||
class RateLimit {
|
||||
constructor (options) {
|
||||
@@ -80,4 +80,4 @@ class RateLimit {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RateLimit
|
||||
export default RateLimit
|
||||
|
||||
@@ -3,12 +3,13 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const jsonrpc = require('jsonrpc-lite')
|
||||
import jsonrpc from 'jsonrpc-lite'
|
||||
|
||||
// Local libraries
|
||||
// const UserLib = require('../../../use-cases/user')
|
||||
const Validators = require('../validators')
|
||||
const RateLimit = require('../rate-limit')
|
||||
import Validators from '../validators.js'
|
||||
|
||||
import RateLimit from '../rate-limit.js'
|
||||
|
||||
class UserRPC {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -533,4 +534,4 @@ class UserRPC {
|
||||
// TODO create deleteUser()
|
||||
}
|
||||
|
||||
module.exports = UserRPC
|
||||
export default UserRPC
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
/* eslint no-useless-catch: 0 */
|
||||
|
||||
// Public npm libraries
|
||||
const jwt = require('jsonwebtoken')
|
||||
import jwt from 'jsonwebtoken'
|
||||
|
||||
// Local libraries
|
||||
const config = require('../../../config')
|
||||
import config from '../../../config/index.js'
|
||||
|
||||
// const UserModel = require('../../adapters/localdb/models/users')
|
||||
|
||||
class Validators {
|
||||
@@ -93,4 +94,4 @@ class Validators {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Validators
|
||||
export default Validators
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
const Passport = require('../../../adapters/passport')
|
||||
import Passport from '../../../adapters/passport.js'
|
||||
const passport = new Passport()
|
||||
|
||||
let _this
|
||||
@@ -96,4 +96,4 @@ class AuthRESTController {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AuthRESTController
|
||||
export default AuthRESTController
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const Router = require('koa-router')
|
||||
import Router from 'koa-router'
|
||||
|
||||
// Local libraries.
|
||||
const AuthRESTController = require('./controller')
|
||||
import AuthRESTController from './controller.js'
|
||||
|
||||
class AuthRouter {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -48,4 +48,4 @@ class AuthRouter {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = AuthRouter
|
||||
export default AuthRouter
|
||||
|
||||
@@ -3,9 +3,9 @@
|
||||
*/
|
||||
|
||||
/* eslint-disable no-useless-escape */
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
|
||||
import ContactLib from '../../../adapters/contact.js'
|
||||
|
||||
const ContactLib = require('../../../adapters/contact')
|
||||
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0'
|
||||
const contactLib = new ContactLib()
|
||||
|
||||
let _this
|
||||
@@ -74,4 +74,4 @@ class ContactController {
|
||||
}
|
||||
}
|
||||
}
|
||||
module.exports = ContactController
|
||||
export default ContactController
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const Router = require('koa-router')
|
||||
import Router from 'koa-router'
|
||||
|
||||
// Local libraries.
|
||||
const ContactRESTControllerLib = require('./controller')
|
||||
import ContactRESTControllerLib from './controller.js'
|
||||
|
||||
class ContactRouter {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -53,4 +53,4 @@ class ContactRouter {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = ContactRouter
|
||||
export default ContactRouter
|
||||
|
||||
@@ -64,4 +64,4 @@ class EntryRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EntryRESTControllerLib
|
||||
export default EntryRESTControllerLib
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const Router = require('koa-router')
|
||||
import Router from 'koa-router'
|
||||
|
||||
// Local libraries.
|
||||
const EntryRESTControllerLib = require('./controller')
|
||||
import EntryRESTControllerLib from './controller.js'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -57,4 +57,4 @@ class UserRouter {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserRouter
|
||||
export default UserRouter
|
||||
|
||||
@@ -6,15 +6,17 @@
|
||||
|
||||
// Public npm libraries.
|
||||
|
||||
// Load the REST API Controllers.
|
||||
const AuthRESTController = require('./auth')
|
||||
const UserRouter = require('./users')
|
||||
const ContactRESTController = require('./contact')
|
||||
const LogsRESTController = require('./logs')
|
||||
const EntryRouter = require('./entry')
|
||||
const OfferRouter = require('./offer')
|
||||
const OrderRouter = require('./order')
|
||||
const P2WDBRouter = require('./p2wdb')
|
||||
// Local libraries
|
||||
import AuthRESTController from './auth/index.js'
|
||||
import UserRouter from './users/index.js'
|
||||
import ContactRESTController from './contact/index.js'
|
||||
import LogsRESTController from './logs/index.js'
|
||||
import IpfsRESTController from './ipfs/index.js'
|
||||
import config from '../../../config/index.js'
|
||||
import EntryRouter from './entry/index.js'
|
||||
import OfferRouter from './offer/index.js'
|
||||
import OrderRouter from './order/index.js'
|
||||
import P2WDBRouter from './p2wdb/index.js'
|
||||
|
||||
class RESTControllers {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -32,7 +34,8 @@ class RESTControllers {
|
||||
)
|
||||
}
|
||||
|
||||
// console.log('Controllers localConfig: ', localConfig)
|
||||
// Encapsulate dependencies
|
||||
this.config = config
|
||||
}
|
||||
|
||||
attachRESTControllers (app) {
|
||||
@@ -41,13 +44,15 @@ class RESTControllers {
|
||||
useCases: this.useCases
|
||||
}
|
||||
|
||||
// Attach the REST API Controllers associated with the /auth route
|
||||
const authRESTController = new AuthRESTController(dependencies)
|
||||
authRESTController.attach(app)
|
||||
if (!this.config.noMongo) {
|
||||
// Attach the REST API Controllers associated with the /auth route
|
||||
const authRESTController = new AuthRESTController(dependencies)
|
||||
authRESTController.attach(app)
|
||||
|
||||
// Attach the REST API Controllers associated with the /user route
|
||||
const userRouter = new UserRouter(dependencies)
|
||||
userRouter.attach(app)
|
||||
// Attach the REST API Controllers associated with the /user route
|
||||
const userRouter = new UserRouter(dependencies)
|
||||
userRouter.attach(app)
|
||||
}
|
||||
|
||||
// Attach the REST API Controllers associated with the /contact route
|
||||
const contactRESTController = new ContactRESTController(dependencies)
|
||||
@@ -57,6 +62,10 @@ class RESTControllers {
|
||||
const logsRESTController = new LogsRESTController(dependencies)
|
||||
logsRESTController.attach(app)
|
||||
|
||||
// Attach the REST API Controllers associated with the /ipfs route
|
||||
const ipfsRESTController = new IpfsRESTController(dependencies)
|
||||
ipfsRESTController.attach(app)
|
||||
|
||||
// Attach the REST API Controllers associated with the /entry route
|
||||
const entryRouter = new EntryRouter(dependencies)
|
||||
entryRouter.attach(app)
|
||||
@@ -72,4 +81,4 @@ class RESTControllers {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RESTControllers
|
||||
export default RESTControllers
|
||||
|
||||
@@ -0,0 +1,145 @@
|
||||
/*
|
||||
REST API Controller library for the /ipfs route
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
|
||||
// Local libraries
|
||||
import wlogger from '../../../adapters/wlogger.js'
|
||||
|
||||
class IpfsRESTControllerLib {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating /ipfs REST Controller.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating /ipfs REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
// this.UserModel = this.adapters.localdb.Users
|
||||
// this.userUseCases = this.useCases.user
|
||||
|
||||
// Bind 'this' object to all subfunctions
|
||||
this.getStatus = this.getStatus.bind(this)
|
||||
this.getPeers = this.getPeers.bind(this)
|
||||
this.getRelays = this.getRelays.bind(this)
|
||||
this.handleError = this.handleError.bind(this)
|
||||
this.connect = this.connect.bind(this)
|
||||
this.getThisNode = this.getThisNode.bind(this)
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /ipfs Get status on IPFS infrastructure
|
||||
* @apiPermission public
|
||||
* @apiName GetIpfsStatus
|
||||
* @apiGroup REST BCH
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5001/ipfs
|
||||
*
|
||||
*/
|
||||
async getStatus (ctx) {
|
||||
try {
|
||||
const status = await this.adapters.ipfs.getStatus()
|
||||
|
||||
ctx.body = { status }
|
||||
} catch (err) {
|
||||
wlogger.error('Error in ipfs/controller.js/getStatus(): ')
|
||||
// ctx.throw(422, err.message)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Return information on IPFS peers this node is connected to.
|
||||
async getPeers (ctx) {
|
||||
try {
|
||||
const showAll = ctx.request.body.showAll
|
||||
|
||||
const peers = await this.adapters.ipfs.getPeers(showAll)
|
||||
|
||||
ctx.body = { peers }
|
||||
} catch (err) {
|
||||
wlogger.error('Error in ipfs/controller.js/getPeers(): ')
|
||||
// ctx.throw(422, err.message)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// Get data about the known Circuit Relays. Hydrate with data from peers list.
|
||||
async getRelays (ctx) {
|
||||
try {
|
||||
const relays = await this.adapters.ipfs.getRelays()
|
||||
|
||||
ctx.body = { relays }
|
||||
} catch (err) {
|
||||
wlogger.error('Error in ipfs/controller.js/getRelays(): ')
|
||||
// ctx.throw(422, err.message)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
async connect (ctx) {
|
||||
try {
|
||||
const multiaddr = ctx.request.body.multiaddr
|
||||
const getDetails = ctx.request.body.getDetails
|
||||
|
||||
// console.log('this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.adapters.ipfs: ', this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.adapters.ipfs)
|
||||
const result = await this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.adapters.ipfs.connectToPeer({ multiaddr, getDetails })
|
||||
// console.log('result: ', result)
|
||||
|
||||
ctx.body = result
|
||||
} catch (err) {
|
||||
wlogger.error('Error in ipfs/controller.js/connect():', err)
|
||||
// ctx.throw(422, err.message)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /ipfs/node Get a copy of the thisNode object from helia-coord
|
||||
* @apiPermission public
|
||||
* @apiName GetThisNode
|
||||
* @apiGroup REST BCH
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5001/ipfs/node
|
||||
*
|
||||
*/
|
||||
async getThisNode (ctx) {
|
||||
try {
|
||||
const thisNode = this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
|
||||
|
||||
ctx.body = { thisNode }
|
||||
} catch (err) {
|
||||
wlogger.error('Error in ipfs/controller.js/getThisNode(): ')
|
||||
// ctx.throw(422, err.message)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// DRY error handler
|
||||
handleError (ctx, err) {
|
||||
// If an HTTP status is specified by the buisiness logic, use that.
|
||||
if (err.status) {
|
||||
if (err.message) {
|
||||
ctx.throw(err.status, err.message)
|
||||
} else {
|
||||
ctx.throw(err.status)
|
||||
}
|
||||
} else {
|
||||
// By default use a 422 error if the HTTP status is not specified.
|
||||
ctx.throw(422, err.message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// module.exports = IpfsRESTControllerLib
|
||||
export default IpfsRESTControllerLib
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
REST API library for the /ipfs route.
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
import Router from 'koa-router'
|
||||
|
||||
// Local libraries.
|
||||
import IPFSRESTControllerLib from './controller.js'
|
||||
import Validators from '../middleware/validators.js'
|
||||
|
||||
// let _this
|
||||
|
||||
class IpfsRouter {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating IPFS REST Controller.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating IPFS REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
const dependencies = {
|
||||
adapters: this.adapters,
|
||||
useCases: this.useCases
|
||||
}
|
||||
|
||||
// Encapsulate dependencies.
|
||||
this.ipfsRESTController = new IPFSRESTControllerLib(dependencies)
|
||||
this.validators = new Validators()
|
||||
|
||||
// Instantiate the router and set the base route.
|
||||
const baseUrl = '/ipfs'
|
||||
this.router = new Router({ prefix: baseUrl })
|
||||
|
||||
// _this = this
|
||||
}
|
||||
|
||||
attach (app) {
|
||||
if (!app) {
|
||||
throw new Error(
|
||||
'Must pass app object when attaching REST API controllers.'
|
||||
)
|
||||
}
|
||||
|
||||
// Define the routes and attach the controller.
|
||||
this.router.get('/', this.ipfsRESTController.getStatus)
|
||||
this.router.post('/peers', this.ipfsRESTController.getPeers)
|
||||
this.router.post('/relays', this.ipfsRESTController.getRelays)
|
||||
this.router.post('/connect', this.ipfsRESTController.connect)
|
||||
this.router.get('/node', this.ipfsRESTController.getThisNode)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(this.router.routes())
|
||||
app.use(this.router.allowedMethods())
|
||||
}
|
||||
}
|
||||
|
||||
// module.exports = BchRouter
|
||||
export default IpfsRouter
|
||||
@@ -1,4 +1,4 @@
|
||||
const LogsApiLib = require('../../../adapters/logapi')
|
||||
import LogsApiLib from '../../../adapters/logapi.js'
|
||||
const logsApiLib = new LogsApiLib()
|
||||
let _this
|
||||
|
||||
@@ -62,4 +62,4 @@ class LogsApi {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LogsApi
|
||||
export default LogsApi
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const Router = require('koa-router')
|
||||
import Router from 'koa-router'
|
||||
|
||||
// Local libraries.
|
||||
const LogsRESTControllerLib = require('./controller')
|
||||
import LogsRESTControllerLib from './controller.js'
|
||||
|
||||
class LogsRouter {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -52,4 +52,4 @@ class LogsRouter {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = LogsRouter
|
||||
export default LogsRouter
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
module.exports = function errorMiddleware () {
|
||||
export default function errorMiddleware () {
|
||||
return async (ctx, next) => {
|
||||
try {
|
||||
await next()
|
||||
@@ -8,4 +8,4 @@ module.exports = function errorMiddleware () {
|
||||
ctx.app.emit('error', err, ctx)
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,11 +1,24 @@
|
||||
/*
|
||||
REST API validator middleware.
|
||||
|
||||
These are a series of functions that ensure the user making a REST API
|
||||
matches a user in the database (or not). In can do fine-grain user control
|
||||
such as telling the difference between an admin, a normal user, and an
|
||||
anonymous user.
|
||||
|
||||
This middleware is used to gatekeep access to different REST API resources.
|
||||
|
||||
CT 9/17/22:
|
||||
This library was lightly refactored to make it work with the new unit tests.
|
||||
This not and the commented code below can be deleted once it is verified that
|
||||
this refactor did not result in any breaking changes.
|
||||
*/
|
||||
|
||||
const User = require('../../../adapters/localdb/models/users')
|
||||
const config = require('../../../../config')
|
||||
const jwt = require('jsonwebtoken')
|
||||
const { wlogger } = require('../../../adapters/wlogger')
|
||||
import User from '../../../adapters/localdb/models/users.js'
|
||||
|
||||
import config from '../../../../config/index.js'
|
||||
import jwt from 'jsonwebtoken'
|
||||
import wlogger from '../../../adapters/wlogger.js'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -20,12 +33,10 @@ class Validators {
|
||||
|
||||
async ensureUser (ctx, next) {
|
||||
try {
|
||||
// console.log(`getToken: ${typeof (getToken)}`)
|
||||
const token = _this.getToken(ctx)
|
||||
|
||||
if (!token) {
|
||||
// console.log(`Err: Token not provided.`)
|
||||
ctx.throw(401)
|
||||
throw new Error('Token could not be retrieved from header')
|
||||
}
|
||||
|
||||
let decoded = null
|
||||
@@ -34,20 +45,23 @@ class Validators {
|
||||
// console.log(`config: ${JSON.stringify(config, null, 2)}`)
|
||||
decoded = _this.jwt.verify(token, config.token)
|
||||
} catch (err) {
|
||||
// console.log(`Err: Token could not be decoded: ${err}`)
|
||||
ctx.throw(401)
|
||||
throw new Error('Could not verify JWT')
|
||||
}
|
||||
|
||||
ctx.state.user = await _this.User.findById(decoded.id, '-password')
|
||||
|
||||
if (!ctx.state.user) {
|
||||
// console.log(`Err: Could not find user.`)
|
||||
ctx.throw(401)
|
||||
// console.log('Err: Could not find user.')
|
||||
throw new Error('Could not find user')
|
||||
}
|
||||
|
||||
// return next()
|
||||
return true
|
||||
} catch (error) {
|
||||
ctx.throw(401)
|
||||
// console.log('Ensure user error: ', error)
|
||||
// console.log('ctx: ', ctx)
|
||||
ctx.status = 401
|
||||
ctx.throw(401, error.message)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,7 +74,8 @@ class Validators {
|
||||
|
||||
if (!token) {
|
||||
// console.log(`Err: Token not provided.`)
|
||||
ctx.throw(401)
|
||||
// ctx.throw(401)
|
||||
throw new Error('Token could not be retrieved from header')
|
||||
}
|
||||
|
||||
let decoded = null
|
||||
@@ -70,22 +85,26 @@ class Validators {
|
||||
decoded = _this.jwt.verify(token, config.token)
|
||||
} catch (err) {
|
||||
// console.log(`Err: Token could not be decoded: ${err}`)
|
||||
ctx.throw(401)
|
||||
// ctx.throw(401)
|
||||
throw new Error('Could not verify JWT')
|
||||
}
|
||||
|
||||
ctx.state.user = await _this.User.findById(decoded.id, '-password')
|
||||
if (!ctx.state.user) {
|
||||
// console.log(`Err: Could not find user.`)
|
||||
ctx.throw(401)
|
||||
// ctx.throw(401)
|
||||
throw new Error('Could not find user')
|
||||
}
|
||||
|
||||
if (ctx.state.user.type !== 'admin') {
|
||||
ctx.throw(401, 'not admin')
|
||||
// ctx.throw(401, 'not admin')
|
||||
throw new Error('User is not an admin')
|
||||
}
|
||||
|
||||
// return next()
|
||||
return true
|
||||
} catch (error) {
|
||||
ctx.status = 401
|
||||
ctx.throw(401, error.message)
|
||||
}
|
||||
}
|
||||
@@ -101,7 +120,8 @@ class Validators {
|
||||
|
||||
if (!token) {
|
||||
// console.log(`Err: Token not provided.`)
|
||||
ctx.throw(401)
|
||||
// ctx.throw(401)
|
||||
throw new Error('Token could not be retrieved from header')
|
||||
}
|
||||
|
||||
// The user ID targeted in this API call.
|
||||
@@ -114,14 +134,16 @@ class Validators {
|
||||
// console.log(`config: ${JSON.stringify(config, null, 2)}`)
|
||||
decoded = _this.jwt.verify(token, config.token)
|
||||
} catch (err) {
|
||||
// console.log(`Err: Token could not be decoded: ${err}`)
|
||||
ctx.throw(401)
|
||||
console.log(`Err: Token could not be decoded: ${err}`)
|
||||
// ctx.throw(401)
|
||||
throw new Error('Could not verify JWT')
|
||||
}
|
||||
|
||||
ctx.state.user = await _this.User.findById(decoded.id, '-password')
|
||||
if (!ctx.state.user) {
|
||||
// console.log(`Err: Could not find user.`)
|
||||
ctx.throw(401)
|
||||
// ctx.throw(401)
|
||||
throw new Error('Could not find user')
|
||||
}
|
||||
// console.log('ctx.state.user: ', ctx.state.user)
|
||||
|
||||
@@ -135,7 +157,8 @@ class Validators {
|
||||
|
||||
// If they don't match, then the calling user better be an admin.
|
||||
if (ctx.state.user.type !== 'admin') {
|
||||
ctx.throw(401, 'not admin')
|
||||
// ctx.throw(401, 'not admin')
|
||||
throw new Error('User is not an admin')
|
||||
} else {
|
||||
wlogger.verbose("It's ok. The user is an admin.")
|
||||
}
|
||||
@@ -144,6 +167,8 @@ class Validators {
|
||||
// return next()
|
||||
return true
|
||||
} catch (error) {
|
||||
// console.log('Error in ensureTargetUserOrAdmin(): ', error)
|
||||
ctx.status = 401
|
||||
ctx.throw(401, error.message)
|
||||
}
|
||||
}
|
||||
@@ -166,4 +191,4 @@ class Validators {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Validators
|
||||
export default Validators
|
||||
|
||||
@@ -2,9 +2,7 @@
|
||||
REST API Controller library for the /offer route
|
||||
*/
|
||||
|
||||
const { wlogger } = require('../../../adapters/wlogger')
|
||||
|
||||
let _this
|
||||
import wlogger from '../../../adapters/wlogger.js'
|
||||
|
||||
class OfferRESTControllerLib {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -26,7 +24,13 @@ class OfferRESTControllerLib {
|
||||
this.OfferModel = this.adapters.localdb.Offer
|
||||
// this.userUseCases = this.useCases.user
|
||||
|
||||
_this = this
|
||||
// Bind 'this' object to all subfunctions.
|
||||
this.createOffer = this.createOffer.bind(this)
|
||||
this.listOffers = this.listOffers.bind(this)
|
||||
this.listNftOffers = this.listNftOffers.bind(this)
|
||||
this.listFungibleOffers = this.listFungibleOffers.bind(this)
|
||||
this.takeOffer = this.takeOffer.bind(this)
|
||||
this.handleError = this.handleError.bind(this)
|
||||
}
|
||||
|
||||
// No api-doc documentation because this wont be a public endpoint
|
||||
@@ -36,7 +40,7 @@ class OfferRESTControllerLib {
|
||||
|
||||
const offerObj = ctx.request.body
|
||||
|
||||
await _this.useCases.offer.createOffer(offerObj)
|
||||
await this.useCases.offer.createOffer(offerObj)
|
||||
|
||||
ctx.body = {
|
||||
success: true
|
||||
@@ -45,7 +49,7 @@ class OfferRESTControllerLib {
|
||||
// console.log(`err.message: ${err.message}`)
|
||||
// console.log('err: ', err)
|
||||
// ctx.throw(422, err.message)
|
||||
_this.handleError(ctx, err)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -55,12 +59,12 @@ class OfferRESTControllerLib {
|
||||
let page = ctx.params.page
|
||||
if (!page) page = 0
|
||||
|
||||
const offers = await _this.useCases.offer.listOffers(page)
|
||||
const offers = await this.useCases.offer.listOffers(page)
|
||||
|
||||
ctx.body = offers
|
||||
} catch (err) {
|
||||
console.log('Error in listOffers REST API handler: ', err)
|
||||
_this.handleError(ctx, err)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -70,12 +74,12 @@ class OfferRESTControllerLib {
|
||||
let page = ctx.params.page
|
||||
if (!page) page = 0
|
||||
|
||||
const offers = await _this.useCases.offer.listNftOffers(page)
|
||||
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)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -85,12 +89,12 @@ class OfferRESTControllerLib {
|
||||
let page = ctx.params.page
|
||||
if (!page) page = 0
|
||||
|
||||
const offers = await _this.useCases.offer.listFungibleOffers(page)
|
||||
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)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,14 +104,14 @@ class OfferRESTControllerLib {
|
||||
try {
|
||||
console.log('REST API controller, body: ', ctx.request.body)
|
||||
|
||||
const offerCid = ctx.request.body.offerCid
|
||||
const nostrEventId = ctx.request.body.nostrEventId
|
||||
|
||||
const hash = await _this.useCases.offer.takeOffer(offerCid)
|
||||
const eventId = await this.useCases.offer.takeOffer(nostrEventId)
|
||||
|
||||
ctx.body = { hash }
|
||||
ctx.body = { eventId }
|
||||
} catch (err) {
|
||||
wlogger.error('Error in takeOffer() REST API handler.')
|
||||
_this.handleError(ctx, err)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -128,4 +132,4 @@ class OfferRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OfferRESTControllerLib
|
||||
export default OfferRESTControllerLib
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const Router = require('koa-router')
|
||||
import Router from 'koa-router'
|
||||
|
||||
// Local libraries.
|
||||
const OfferRESTControllerLib = require('./controller')
|
||||
import OfferRESTControllerLib from './controller.js'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -48,8 +48,13 @@ class OfferRouter {
|
||||
)
|
||||
}
|
||||
|
||||
// 12/3/24 CT:
|
||||
// Note: The createOffer() path was used by a P2WDB webhook to generate an
|
||||
// Offer from and Order. This has been deprecated and Offers are now created
|
||||
// by a Timer Controller monitoring a Nostr topic.
|
||||
|
||||
// Define the routes and attach the controller.
|
||||
this.router.post('/', _this.offerRESTController.createOffer)
|
||||
// this.router.post('/', _this.offerRESTController.createOffer) // Deprecated.
|
||||
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)
|
||||
@@ -61,4 +66,4 @@ class OfferRouter {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OfferRouter
|
||||
export default OfferRouter
|
||||
|
||||
@@ -35,6 +35,7 @@ class OrderRESTControllerLib {
|
||||
// console.log('body: ', ctx.request.body)
|
||||
|
||||
const orderObj = ctx.request.body.order
|
||||
console.log('orderObj: ', orderObj)
|
||||
|
||||
const hash = await _this.useCases.order.createOrder(orderObj)
|
||||
|
||||
@@ -47,6 +48,41 @@ 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)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete an existing order by returning the token to the root address of
|
||||
// the DEX wallet.
|
||||
async deleteOrder (ctx) {
|
||||
try {
|
||||
// console.log('body: ', ctx.request.body)
|
||||
|
||||
const nostrEventId = ctx.request.body.nostrEventId
|
||||
// console.log('p2wdbHash: ', p2wdbHash)
|
||||
|
||||
const txid = await _this.useCases.order.deleteOrder(nostrEventId)
|
||||
|
||||
ctx.body = { txid }
|
||||
} catch (err) {
|
||||
// console.log(`err.message: ${err.message}`)
|
||||
// console.log('err: ', err)
|
||||
// ctx.throw(422, err.message)
|
||||
_this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// DRY error handler
|
||||
handleError (ctx, err) {
|
||||
console.log('err', err)
|
||||
@@ -65,4 +101,4 @@ class OrderRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OrderRESTControllerLib
|
||||
export default OrderRESTControllerLib
|
||||
|
||||
@@ -3,10 +3,10 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const Router = require('koa-router')
|
||||
import Router from 'koa-router'
|
||||
|
||||
// Local libraries.
|
||||
const OrderRESTControllerLib = require('./controller')
|
||||
import OrderRESTControllerLib from './controller.js'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -50,6 +50,8 @@ class OrderRouter {
|
||||
|
||||
// Define the routes and attach the controller.
|
||||
this.router.post('/', _this.orderRESTController.createOrder)
|
||||
this.router.get('/list/all/:page', _this.orderRESTController.listOrders)
|
||||
this.router.post('/delete', _this.orderRESTController.deleteOrder)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(_this.router.routes())
|
||||
@@ -57,4 +59,4 @@ class OrderRouter {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OrderRouter
|
||||
export default OrderRouter
|
||||
|
||||
@@ -101,4 +101,4 @@ class P2WDBRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = P2WDBRESTControllerLib
|
||||
export default P2WDBRESTControllerLib
|
||||
|
||||
@@ -5,10 +5,10 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const Router = require('koa-router')
|
||||
import Router from 'koa-router'
|
||||
|
||||
// Local libraries.
|
||||
const P2WDBRESTControllerLib = require('./controller')
|
||||
import P2WDBRESTControllerLib from './controller.js'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -59,4 +59,4 @@ class P2WDBRouter {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = P2WDBRouter
|
||||
export default P2WDBRouter
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
REST API Controller library for the /user route
|
||||
*/
|
||||
|
||||
const { wlogger } = require('../../../adapters/wlogger')
|
||||
import wlogger from '../../../adapters/wlogger.js'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -36,7 +36,7 @@ class UserRESTControllerLib {
|
||||
* @apiGroup REST Users
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "user": { "email": "email@format.com", "name": "my name", "password": "secretpasas" } }' localhost:5001/users
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "user": { "email": "email@format.com", "name": "my name", "password": "secretpasas" } }' localhost:5010/users
|
||||
*
|
||||
* @apiParam {Object} user User object (required)
|
||||
* @apiParam {String} user.email Email
|
||||
@@ -124,7 +124,7 @@ class UserRESTControllerLib {
|
||||
|
||||
ctx.body = { users }
|
||||
} catch (err) {
|
||||
wlogger.error('Error in users/controller.js/getUsers(): '.err)
|
||||
wlogger.error('Error in users/controller.js/getUsers(): ', err)
|
||||
ctx.throw(422, err.message)
|
||||
}
|
||||
}
|
||||
@@ -136,7 +136,7 @@ class UserRESTControllerLib {
|
||||
* @apiGroup REST Users
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5000/users/56bd1da600a526986cf65c80
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5010/users/56bd1da600a526986cf65c80
|
||||
*
|
||||
* @apiSuccess {Object} users User object
|
||||
* @apiSuccess {ObjectId} users._id User id
|
||||
@@ -281,4 +281,4 @@ class UserRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserRESTControllerLib
|
||||
export default UserRESTControllerLib
|
||||
|
||||
@@ -3,11 +3,12 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const Router = require('koa-router')
|
||||
import Router from 'koa-router'
|
||||
|
||||
// Local libraries.
|
||||
const UserRESTControllerLib = require('./controller')
|
||||
const Validators = require('../middleware/validators')
|
||||
import UserRESTControllerLib from './controller.js'
|
||||
|
||||
import Validators from '../middleware/validators.js'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -85,4 +86,4 @@ class UserRouter {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserRouter
|
||||
export default UserRouter
|
||||
|
||||
@@ -3,8 +3,7 @@
|
||||
kicked off periodicially.
|
||||
*/
|
||||
|
||||
// Used to retain scope of 'this', when the scope is lost.
|
||||
let _this
|
||||
import config from '../../config/index.js'
|
||||
|
||||
class TimerControllers {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -23,37 +22,83 @@ class TimerControllers {
|
||||
}
|
||||
|
||||
this.debugLevel = localConfig.debugLevel
|
||||
this.config = config
|
||||
|
||||
_this = this
|
||||
|
||||
this.startTimers()
|
||||
// Bind 'this' object to all subfunctions
|
||||
this.startTimers = this.startTimers.bind(this)
|
||||
this.gcOrders = this.gcOrders.bind(this)
|
||||
this.gcOffers = this.gcOffers.bind(this)
|
||||
this.checkDupOffers = this.checkDupOffers.bind(this)
|
||||
this.loadOffers = this.loadOffers.bind(this)
|
||||
// State
|
||||
this.gcOrdersInt = null
|
||||
this.gcOffersInt = null
|
||||
this.checkDupOffersInt = null
|
||||
this.loadOffersInt = null
|
||||
}
|
||||
|
||||
// Start all the time-based controllers.
|
||||
startTimers () {
|
||||
setInterval(this.gcOrders, 60000 * 5)
|
||||
setInterval(this.gcOffers, 60000 * 5)
|
||||
this.gcOrdersInt = setInterval(this.gcOrders, 60000 * 5)
|
||||
this.gcOffersInt = setInterval(this.gcOffers, 60000 * 5)
|
||||
this.checkDupOffersInt = setInterval(this.checkDupOffers, 60000 * 4.5)
|
||||
this.loadOffersInt = setInterval(this.loadOffers, 60000 * 2)
|
||||
return true
|
||||
}
|
||||
|
||||
stopTimers () {
|
||||
clearInterval(this.gcOrdersInt)
|
||||
clearInterval(this.gcOffersInt)
|
||||
clearInterval(this.checkDupOffers)
|
||||
}
|
||||
|
||||
// Garbage Collect the Orders.
|
||||
gcOrders () {
|
||||
try {
|
||||
_this.useCases.order.removeStaleOrders()
|
||||
this.useCases.order.removeStaleOrders()
|
||||
return true
|
||||
} catch (err) {
|
||||
// Do not throw an error. This is a top-level function.
|
||||
console.log('Error in timer-controllers.js/gcOrders(): ', err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Garbage Collect the Offers.
|
||||
gcOffers () {
|
||||
try {
|
||||
_this.useCases.offer.removeStaleOffers()
|
||||
this.useCases.offer.removeStaleOffers()
|
||||
return true
|
||||
} catch (err) {
|
||||
// Do not throw an error. This is a top-level function.
|
||||
console.log('Error in timer-controllers.js/gcOffers(): ', err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Remove duplicate Offers
|
||||
checkDupOffers () {
|
||||
try {
|
||||
this.useCases.offer.removeDuplicateOffers()
|
||||
return true
|
||||
} catch (err) {
|
||||
// Do not throw an error. This is a top-level function.
|
||||
console.log('Error in timer-controllers.js/checkDupOffers(): ', err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
// Load offers From nostr .
|
||||
async loadOffers () {
|
||||
try {
|
||||
await this.useCases.offer.loadOffers()
|
||||
return true
|
||||
} catch (err) {
|
||||
// Do not throw an error. This is a top-level function.
|
||||
console.log('Error in timer-controllers.js/loadOffers(): ', err)
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TimerControllers
|
||||
export default TimerControllers
|
||||
|
||||
@@ -38,4 +38,4 @@ class Entry {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Entry
|
||||
export default Entry
|
||||
|
||||
@@ -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.")
|
||||
}
|
||||
@@ -103,4 +103,4 @@ class OfferEntity {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OfferEntity
|
||||
export default OfferEntity
|
||||
|
||||
+11
-9
@@ -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,12 +59,12 @@ class Order {
|
||||
rateInBaseUnit,
|
||||
minUnitsToExchange,
|
||||
numTokens,
|
||||
makerAddr,
|
||||
ticker
|
||||
makerAddr
|
||||
// ticker
|
||||
}
|
||||
|
||||
return offerData
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = Order
|
||||
export default Order
|
||||
|
||||
@@ -21,4 +21,4 @@ class User {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = User
|
||||
export default User
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
const { wlogger } = require('../adapters/wlogger')
|
||||
|
||||
const EntryEntiy = require('../entities/entry')
|
||||
// local libraries
|
||||
import wlogger from '../adapters/wlogger.js'
|
||||
import EntryEntiy from '../entities/entry.js'
|
||||
|
||||
class EntryLib {
|
||||
constructor (localConfig = {}) {
|
||||
// console.log('User localConfig: ', localConfig)
|
||||
@@ -63,4 +65,4 @@ class EntryLib {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = EntryLib
|
||||
export default EntryLib
|
||||
|
||||
+5
-11
@@ -4,10 +4,10 @@
|
||||
https://troutsblog.com/blog/clean-architecture
|
||||
*/
|
||||
|
||||
const UserUseCases = require('./user')
|
||||
const EntryUseCases = require('./entry')
|
||||
const OfferUseCases = require('./offer')
|
||||
const OrderUseCases = require('./order')
|
||||
import UserUseCases from './user.js'
|
||||
import EntryUseCases from './entry.js'
|
||||
import OfferUseCases from './offer/index.js'
|
||||
import OrderUseCases from './order.js'
|
||||
|
||||
class UseCases {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -28,16 +28,10 @@ class UseCases {
|
||||
|
||||
// Run any startup Use Cases at the start of the app.
|
||||
async start () {
|
||||
// try {
|
||||
console.log('Async Use Cases have been started.')
|
||||
|
||||
return true
|
||||
// } catch (err) {
|
||||
// console.error('Error in use-cases/index.js/start()')
|
||||
// // console.log(err)
|
||||
// throw err
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UseCases
|
||||
export default UseCases
|
||||
|
||||
+209
-108
@@ -14,11 +14,12 @@
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
const axios = require('axios')
|
||||
import axios from 'axios'
|
||||
import RetryQueue from '@chris.troutner/retry-queue'
|
||||
|
||||
// Local libraries
|
||||
const OfferEntity = require('../../entities/offer')
|
||||
const config = require('../../../config')
|
||||
import OfferEntity from '../../entities/offer.js'
|
||||
import config from '../../../config/index.js'
|
||||
|
||||
const DEFAULT_ENTRIES_PER_PAGE = 20
|
||||
const NFT_ENTRIES_PER_PAGE = 6
|
||||
@@ -36,27 +37,30 @@ class OfferUseCases {
|
||||
this.orderUseCase = localConfig.order
|
||||
if (!this.orderUseCase) {
|
||||
throw new Error(
|
||||
'Instance of Offer Use Cases must be passed in when instantiating Offer Use Cases library.'
|
||||
'Instance of Order Use Cases must be passed in when instantiating Offer Use Cases library.'
|
||||
)
|
||||
}
|
||||
|
||||
// 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.loadOffers = this.loadOffers.bind(this)
|
||||
}
|
||||
|
||||
// This method is called by the POST /offer REST API controller, which is
|
||||
// triggered by a P2WDB webhook.
|
||||
// This method is called by timer controller to load offers from a Nostr topic.
|
||||
async createOffer (offerObj) {
|
||||
try {
|
||||
console.log('Use Case createOffer(offerObj): ', offerObj)
|
||||
|
||||
// Return if Offer already exists in database with the same P2WDB CID.
|
||||
// Return if Offer already exists in database with the same utxo transaction id.
|
||||
try {
|
||||
await this.findOfferByHash(offerObj.hash)
|
||||
await this.findOfferByTxid(offerObj.data.utxoTxid)
|
||||
|
||||
console.log('Offer already found in local database.')
|
||||
return false
|
||||
@@ -67,41 +71,47 @@ class OfferUseCases {
|
||||
// Input Validation
|
||||
// TODO: This is a hack. Find a better way to protect against the corner-
|
||||
// case of counter-offers getting routed here.
|
||||
if (offerObj.data.dataType === 'counter-offer') {
|
||||
console.log('WARN: Counter Offer innappropriately routed to createOffer()')
|
||||
}
|
||||
// if (offerObj.data.dataType === 'counter-offer') {
|
||||
// console.log('WARN: Counter Offer innappropriately routed to createOffer()')
|
||||
// }
|
||||
|
||||
// Verify that UTXO in offer is unspent. If it is spent, then ignore the
|
||||
// offer.
|
||||
const txid = offerObj.data.utxoTxid
|
||||
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'
|
||||
|
||||
// Set timestamp
|
||||
offerObj.timestamp = new Date().getTime()
|
||||
|
||||
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)
|
||||
// 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.
|
||||
@@ -110,7 +120,7 @@ class OfferUseCases {
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
console.error('Error in createOffer()')
|
||||
console.error('Error in createOffer()', err.message)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -131,8 +141,9 @@ class OfferUseCases {
|
||||
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 url = `https://${cid}.ipfs.w3s.link/data.json`
|
||||
const url = `${this.config.ipfsGateway}${cid}/data.json`
|
||||
const result = await this.axios.get(url)
|
||||
const mutableData = result.data
|
||||
console.log(`mutableData: ${JSON.stringify(mutableData, null, 2)}`)
|
||||
|
||||
@@ -164,7 +175,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)}`)
|
||||
|
||||
@@ -199,11 +210,11 @@ class OfferUseCases {
|
||||
async listOffers (page = 0) {
|
||||
try {
|
||||
const data = await this.OfferModel.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
|
||||
@@ -219,11 +230,11 @@ class OfferUseCases {
|
||||
displayCategory: { $ne: 'fungible' },
|
||||
nsfw
|
||||
})
|
||||
// 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 * NFT_ENTRIES_PER_PAGE)
|
||||
// Only return 20 results.
|
||||
// Only return 20 results.
|
||||
.limit(NFT_ENTRIES_PER_PAGE)
|
||||
|
||||
// console.log('listNftOffers() returning this data: ', data)
|
||||
@@ -238,11 +249,11 @@ class OfferUseCases {
|
||||
async listFungibleOffers (page = 0) {
|
||||
try {
|
||||
const data = await this.OfferModel.find({ displayCategory: 'fungible' })
|
||||
// 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 * FUNGIBLE_ENTRIES_PER_PAGE)
|
||||
// Only return 20 results.
|
||||
// Only return 20 results.
|
||||
.limit(FUNGIBLE_ENTRIES_PER_PAGE)
|
||||
|
||||
// console.log('listFungibleOffers() returning this data: ', data)
|
||||
@@ -262,12 +273,12 @@ class OfferUseCases {
|
||||
// provide functionality to take less than the total amount of tokens offered
|
||||
// (offerInfo.numTokens). Taking less than the offered amount will be added
|
||||
// in the future.
|
||||
async takeOffer (offerCid) {
|
||||
async takeOffer (eventId) {
|
||||
try {
|
||||
console.log('offerCid: ', offerCid)
|
||||
if (!eventId || typeof eventId !== 'string') throw new Error('eventId must be a string')
|
||||
|
||||
// Get the Offer information
|
||||
const offerInfo = await this.findOfferByHash(offerCid)
|
||||
const offerInfo = await this.findOfferByEvent(eventId)
|
||||
console.log(`offerInfo: ${JSON.stringify(offerInfo, null, 2)}`)
|
||||
|
||||
// Ensure the offer is in a 'posted' state and not already 'taken'
|
||||
@@ -276,18 +287,13 @@ 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
|
||||
}
|
||||
|
||||
// Note : should be added to retry-queue?
|
||||
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}`)
|
||||
@@ -305,7 +311,8 @@ 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))
|
||||
console.log('satsToMove', satsToMove, offerInfo)
|
||||
if (isNaN(satsToMove)) {
|
||||
throw new Error('Could not calculate the amount of BCH to generate counter offer')
|
||||
}
|
||||
@@ -331,9 +338,9 @@ class OfferUseCases {
|
||||
// Create valid Offer object
|
||||
const takenOfferInfo = Object.assign({}, offerInfo)
|
||||
takenOfferInfo.partialTxHex = partialTxHex
|
||||
delete takenOfferInfo.p2wdbHash
|
||||
delete takenOfferInfo.nostrEventId
|
||||
delete takenOfferInfo._id
|
||||
takenOfferInfo.offerHash = offerInfo.p2wdbHash
|
||||
takenOfferInfo.offerHash = offerInfo.nostrEventId
|
||||
|
||||
// Add P2WDB specific flag for signaling that this is a new offer.
|
||||
takenOfferInfo.dataType = 'counter-offer'
|
||||
@@ -341,19 +348,19 @@ class OfferUseCases {
|
||||
// Write offer info to the P2WDB
|
||||
// TODO: This will trigger the webhook. Find some way of triggering the
|
||||
// webhook on new offers, but not on counteroffers
|
||||
const p2wdbObj = {
|
||||
const nostrData = {
|
||||
wif: this.adapters.wallet.bchWallet.walletInfo.privateKey,
|
||||
data: takenOfferInfo,
|
||||
appId: this.config.p2wdbAppId
|
||||
}
|
||||
const hash = await this.adapters.p2wdb.write(p2wdbObj)
|
||||
const resultEventId = await this.adapters.nostr.post(nostrData)
|
||||
|
||||
// Delete the Offer from the database, so that the user doesn't attempt
|
||||
// to take the offer more than once.
|
||||
offerInfo.remove()
|
||||
|
||||
// Return the P2WDB CID
|
||||
return hash
|
||||
return resultEventId
|
||||
|
||||
// return 'fake-hash'
|
||||
} catch (err) {
|
||||
@@ -376,15 +383,16 @@ class OfferUseCases {
|
||||
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)
|
||||
if (!canWriteToP2WDB) throw new Error('App wallet does not have funds for writing to the P2WDB.')
|
||||
// Note : this validation should be deprecated for nostr functionality?
|
||||
// const wif = this.adapters.wallet.bchWallet.walletInfo.privateKey
|
||||
// const canWriteToP2WDB = await this.adapters.p2wdb.checkForSufficientFunds(wif)
|
||||
// if (!canWriteToP2WDB) throw new Error('App wallet does not have funds for writing to the P2WDB.')
|
||||
|
||||
if (offerEntity.buyOrSell.includes('sell')) {
|
||||
// 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!')
|
||||
}
|
||||
@@ -397,7 +405,7 @@ class OfferUseCases {
|
||||
throw new Error('App wallet does not control enough BCH to purchase the tokens.')
|
||||
}
|
||||
|
||||
//
|
||||
//
|
||||
} else {
|
||||
// Buy Offer
|
||||
throw new Error('Buy offers are not supported yet.')
|
||||
@@ -416,44 +424,69 @@ class OfferUseCases {
|
||||
}
|
||||
}
|
||||
|
||||
async findOfferByHash (p2wdbHash) {
|
||||
// try {
|
||||
if (typeof p2wdbHash !== 'string' || !p2wdbHash) {
|
||||
throw new Error('p2wdbHash must be a string')
|
||||
// Retrieve an Order model from the database. Find it by its event Id.
|
||||
async findOfferByEvent (nostrEventId) {
|
||||
try {
|
||||
if (typeof nostrEventId !== 'string' || !nostrEventId) {
|
||||
throw new Error('nostrEventId must be a string')
|
||||
}
|
||||
|
||||
const order = await this.OfferModel.findOne({ nostrEventId })
|
||||
|
||||
if (!order) {
|
||||
throw new Error('offer not found')
|
||||
}
|
||||
|
||||
const orderObject = order.toObject()
|
||||
// return this.offerEntity.validateFromModel(offerObject)
|
||||
|
||||
return orderObject
|
||||
} catch (err) {
|
||||
console.error('Error in findOfferByEvent()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
const offer = await this.OfferModel.findOne({ p2wdbHash })
|
||||
async findOfferByTxid (utxoTxid) {
|
||||
try {
|
||||
// try {
|
||||
if (typeof utxoTxid !== 'string' || !utxoTxid) {
|
||||
throw new Error('utxoTxid must be a string')
|
||||
}
|
||||
|
||||
if (!offer) {
|
||||
throw new Error('offer not found')
|
||||
const offer = await this.OfferModel.findOne({ utxoTxid })
|
||||
|
||||
// TODO: Offer should be found by TXID, then if there is more than one
|
||||
// result, they should be filtered by the vout property. That will leave
|
||||
// one remaining UTXO.
|
||||
|
||||
if (!offer) {
|
||||
throw new Error('offer not found')
|
||||
}
|
||||
|
||||
return offer
|
||||
} catch (error) {
|
||||
console.error('Error in use-cases/offer/findOfferByTxid(): ', error.message)
|
||||
throw error
|
||||
}
|
||||
|
||||
return offer
|
||||
|
||||
// const offerObject = offer.toObject()
|
||||
// return this.offerEntity.validateFromModel(offerObject)
|
||||
|
||||
// return offerObject
|
||||
// } catch (err) {
|
||||
// // console.error('Error in findOffer(): ', err)
|
||||
// throw err
|
||||
// }
|
||||
}
|
||||
|
||||
// This function is called by the P2WDB webhook REST API handler. When a
|
||||
// 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
|
||||
// and broadcasts the transaction to accept the Counter Offer.
|
||||
async acceptCounterOffer (p2wdbData) {
|
||||
async acceptCounterOffer (offerData) {
|
||||
try {
|
||||
console.log(`acceptCounterOffer() p2wdbData: ${JSON.stringify(p2wdbData, null, 2)}`)
|
||||
console.log(`acceptCounterOffer() offerData: ${JSON.stringify(offerData, null, 2)}`)
|
||||
|
||||
// See if this instance of bch-dex is managing the Order associated with
|
||||
// the incoming Counter Offer.
|
||||
const orderHash = p2wdbData.data.offerHash
|
||||
|
||||
// Note : this should be handle by nostrEvent id or UtxoId?
|
||||
const orderHash = offerData.data.nostrEventId
|
||||
let orderData = {}
|
||||
try {
|
||||
orderData = await this.orderUseCase.findOrderByHash(orderHash)
|
||||
orderData = await this.orderUseCase.findOrderByEvent(orderHash)
|
||||
console.log(`orderData: ${JSON.stringify(orderData, null, 2)}`)
|
||||
} catch (err) {
|
||||
console.log('Order matching this Counter Offer is not managed by this instance of bch-dex. Exiting.')
|
||||
@@ -461,12 +494,13 @@ class OfferUseCases {
|
||||
}
|
||||
|
||||
// Deserialize the partially signed transaction.
|
||||
const txHex = p2wdbData.data.partialTxHex
|
||||
const txHex = offerData.data.partialTxHex
|
||||
const txObj = await this.adapters.wallet.deseralizeTx(txHex)
|
||||
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))
|
||||
console.log('Ceil', satsToReceive)
|
||||
if (isNaN(satsToReceive)) {
|
||||
throw new Error('Could not calculate the amount of BCH offered in the Counter Offer')
|
||||
}
|
||||
@@ -496,6 +530,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.
|
||||
@@ -514,12 +587,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,
|
||||
@@ -527,7 +594,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.
|
||||
@@ -544,7 +612,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()
|
||||
}
|
||||
@@ -564,33 +633,65 @@ class OfferUseCases {
|
||||
}
|
||||
}
|
||||
|
||||
async flagOffer (flagData) {
|
||||
console.log(`flagData: ${JSON.stringify(flagData, null, 2)}`)
|
||||
// Flag offers as NSFW.
|
||||
async flagOffer (flagData = {}) {
|
||||
try {
|
||||
if (!flagData.data) throw new Error('"data" property is required')
|
||||
|
||||
const p2wdbHash = flagData.data.p2wdbHash
|
||||
console.log(`flagData: ${JSON.stringify(flagData, null, 2)}`)
|
||||
|
||||
// Get the offer from the database.
|
||||
const offer = await this.findOfferByHash(p2wdbHash)
|
||||
console.log(`Flagging this offer: ${JSON.stringify(offer, null, 2)}`)
|
||||
const eventId = flagData.data.nostrEventId
|
||||
|
||||
if (!offer) {
|
||||
throw new Error(`Offer ${p2wdbHash} not found in the database.`)
|
||||
// Get the offer from the database.
|
||||
const offer = await this.findOfferByEvent(eventId)
|
||||
console.log(`Flagging this offer: ${JSON.stringify(offer, null, 2)}`)
|
||||
|
||||
if (!offer) {
|
||||
throw new Error(`Offer ${eventId} 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
|
||||
} catch (error) {
|
||||
console.error('Error in flagOffer(): ', error)
|
||||
throw error
|
||||
}
|
||||
}
|
||||
|
||||
// Add the raw flag data to the database model.
|
||||
offer.flags.push(flagData)
|
||||
// Get offers data from nostr.
|
||||
async loadOffers () {
|
||||
try {
|
||||
// Retrieve offers array.
|
||||
const offers = await this.adapters.nostr.read()
|
||||
for (let i = 0; i < offers.length; i++) {
|
||||
try {
|
||||
const offer = offers[i]
|
||||
|
||||
// If flag count is 3 or more, mark the Offer as NSFW
|
||||
const flagCnt = offer.flags.length
|
||||
if (flagCnt >= 3) {
|
||||
offer.nsfw = true
|
||||
// offer data
|
||||
const offerObj = JSON.parse(offer)
|
||||
|
||||
// Try to create new offer
|
||||
await this.createOffer(offerObj)
|
||||
} catch (error) {
|
||||
/* exit quietly */
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error in loadOffers(): ', error)
|
||||
throw error
|
||||
}
|
||||
|
||||
// Save the updated offer data to the database.
|
||||
await offer.save()
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OfferUseCases
|
||||
export default OfferUseCases
|
||||
|
||||
@@ -9,8 +9,9 @@
|
||||
pay-to-write-access-controller.js depends on this library.
|
||||
*/
|
||||
|
||||
const PQueue = require('p-queue').default
|
||||
const pRetry = require('p-retry')
|
||||
// Global npm libraries
|
||||
import PQueue from 'p-queue'
|
||||
import pRetry from 'p-retry'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -103,4 +104,4 @@ class RetryQueue {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RetryQueue
|
||||
export default RetryQueue
|
||||
|
||||
+87
-37
@@ -2,10 +2,15 @@
|
||||
Order use-case library.
|
||||
*/
|
||||
|
||||
// Global npm libraries
|
||||
import RetryQueue from '@chris.troutner/retry-queue'
|
||||
|
||||
// Local libraries
|
||||
const { wlogger } = require('../adapters/wlogger')
|
||||
const OrderEntity = require('../entities/order')
|
||||
const config = require('../../config')
|
||||
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 = {}) {
|
||||
@@ -22,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.
|
||||
@@ -36,33 +45,37 @@ 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)
|
||||
|
||||
// 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, {})
|
||||
|
||||
// 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)
|
||||
console.log('utxoInfo: ', utxoInfo)
|
||||
// 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
|
||||
@@ -72,24 +85,24 @@ class OrderLib {
|
||||
// Add P2WDB specific flag for signaling that this is a new offer.
|
||||
orderEntity.dataType = 'offer'
|
||||
|
||||
// Add order to P2WDB.
|
||||
const p2wdbObj = {
|
||||
wif: this.adapters.wallet.bchWallet.walletInfo.privateKey,
|
||||
data: orderEntity,
|
||||
appId: this.config.p2wdbAppId
|
||||
// Post the new Order information to Nostr under the topic set in the
|
||||
// config file.
|
||||
const postObj = {
|
||||
data: orderEntity
|
||||
}
|
||||
const hash = await this.adapters.p2wdb.write(p2wdbObj)
|
||||
const postMsg = JSON.stringify(postObj)
|
||||
const eventId = await this.adapters.nostr.post(postMsg)
|
||||
// console.log('hash: ', hash)
|
||||
|
||||
// Create a MongoDB model to hold the Order
|
||||
orderEntity.hdIndex = utxoInfo.hdIndex
|
||||
orderEntity.p2wdbHash = hash
|
||||
orderEntity.nostrEventId = eventId
|
||||
|
||||
console.log(`creating new order model: ${JSON.stringify(orderEntity, null, 2)}`)
|
||||
const order = new this.OrderModel(orderEntity)
|
||||
await order.save()
|
||||
|
||||
return hash
|
||||
return eventId
|
||||
} catch (err) {
|
||||
// console.log("Error in use-cases/entry.js/createEntry()", err.message)
|
||||
wlogger.error('Error in use-cases/order.js/createOrder())')
|
||||
@@ -107,6 +120,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
|
||||
@@ -122,12 +136,10 @@ class OrderLib {
|
||||
|
||||
// Combine Fungible and NFT token UTXOs.
|
||||
let tokenUtxos = utxos.slpUtxos.type1.tokens.concat(utxos.slpUtxos.nft.tokens)
|
||||
|
||||
// Get token UTXOs that match the token in the order.
|
||||
tokenUtxos = tokenUtxos.filter(
|
||||
x => x.tokenId === orderEntity.tokenId
|
||||
)
|
||||
console.log('tokenUtxos: ', tokenUtxos)
|
||||
|
||||
// Get the total amount of tokens in the wallet that match the token
|
||||
// in the order.
|
||||
@@ -156,14 +168,14 @@ class OrderLib {
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve an Order model from the database. Find it by its P2WDB CID.
|
||||
async findOrderByHash (p2wdbHash) {
|
||||
// Retrieve an Order model from the database. Find it by its event Id.
|
||||
async findOrderByEvent (nostrEventId) {
|
||||
try {
|
||||
if (typeof p2wdbHash !== 'string' || !p2wdbHash) {
|
||||
throw new Error('p2wdbHash must be a string')
|
||||
if (typeof nostrEventId !== 'string' || !nostrEventId) {
|
||||
throw new Error('nostrEventId must be a string')
|
||||
}
|
||||
|
||||
const order = await this.OrderModel.findOne({ p2wdbHash })
|
||||
const order = await this.OrderModel.findOne({ nostrEventId })
|
||||
|
||||
if (!order) {
|
||||
throw new Error('order not found')
|
||||
@@ -174,7 +186,7 @@ class OrderLib {
|
||||
|
||||
return orderObject
|
||||
} catch (err) {
|
||||
console.error('Error in findOrder()')
|
||||
console.error('Error in findOrderByEvent()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -189,7 +201,7 @@ class OrderLib {
|
||||
|
||||
// Get all Orders in the database.
|
||||
const orders = await this.OrderModel.find({})
|
||||
// console.log('orders: ', orders)
|
||||
console.log('orders: ', orders)
|
||||
|
||||
// Loop through each Order and ensure the UTXO is still valid.
|
||||
for (let i = 0; i < orders.length; i++) {
|
||||
@@ -208,7 +220,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.
|
||||
@@ -225,7 +238,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()
|
||||
}
|
||||
@@ -235,6 +249,42 @@ 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
|
||||
}
|
||||
}
|
||||
|
||||
// Delete an Order model by sending the token back to the root address. This
|
||||
// will allow the garbage collectors to delete the Order and the Offer.
|
||||
async deleteOrder (nostrEventId) {
|
||||
try {
|
||||
// Find the order by the given hash.
|
||||
const order = await this.findOrderByEvent(nostrEventId)
|
||||
console.log('order: ', order)
|
||||
|
||||
// Reclaim the tokens
|
||||
const txid = await this.adapters.wallet.reclaimTokens(order)
|
||||
|
||||
return txid
|
||||
} catch (err) {
|
||||
console.error('Error in use-cases/order/deleteOrder()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = OrderLib
|
||||
export default OrderLib
|
||||
|
||||
@@ -3,8 +3,9 @@
|
||||
functions are called by the /user REST API endpoints.
|
||||
*/
|
||||
|
||||
const UserEntity = require('../entities/user')
|
||||
const { wlogger } = require('../adapters/wlogger')
|
||||
import UserEntity from '../entities/user.js'
|
||||
|
||||
import wlogger from '../adapters/wlogger.js'
|
||||
|
||||
class UserLib {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -181,4 +182,4 @@ class UserLib {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = UserLib
|
||||
export default UserLib
|
||||
|
||||
@@ -5,15 +5,17 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const assert = require('chai').assert
|
||||
const axios = require('axios').default
|
||||
import { assert } from 'chai'
|
||||
|
||||
import axios from 'axios'
|
||||
|
||||
// const sinon = require('sinon')
|
||||
|
||||
// Local support libraries
|
||||
const config = require('../../../config')
|
||||
const Server = require('../../../bin/server')
|
||||
const testUtils = require('../../utils/test-utils')
|
||||
const AdminLib = require('../../../src/adapters/admin')
|
||||
import config from '../../../config/index.js'
|
||||
import Server from '../../../bin/server.js'
|
||||
import testUtils from '../../utils/test-utils.js'
|
||||
import AdminLib from '../../../src/adapters/admin.js'
|
||||
const adminLib = new AdminLib()
|
||||
|
||||
// const request = supertest.agent(app.listen())
|
||||
@@ -21,105 +23,107 @@ const context = {}
|
||||
|
||||
const LOCALHOST = `http://localhost:${config.port}`
|
||||
|
||||
describe('Auth', () => {
|
||||
before(async () => {
|
||||
const app = new Server()
|
||||
if (!config.noMongo) {
|
||||
describe('Auth', () => {
|
||||
before(async () => {
|
||||
const app = new Server()
|
||||
|
||||
// This should be the first instruction. It starts the REST API server.
|
||||
await app.startServer()
|
||||
// This should be the first instruction. It starts the REST API server.
|
||||
await app.startServer()
|
||||
|
||||
// Stop the IPFS node for the rest of the e2e tests.
|
||||
// await app.controllers.adapters.ipfs.stop()
|
||||
// Stop the IPFS node for the rest of the e2e tests.
|
||||
// await app.controllers.adapters.ipfs.stop()
|
||||
|
||||
// Delete all previous users in the database.
|
||||
await testUtils.deleteAllUsers()
|
||||
// Delete all previous users in the database.
|
||||
await testUtils.deleteAllUsers()
|
||||
|
||||
// Create a new admin user.
|
||||
await adminLib.createSystemUser()
|
||||
// Create a new admin user.
|
||||
await adminLib.createSystemUser()
|
||||
|
||||
const userObj = {
|
||||
email: 'test@test.com',
|
||||
password: 'pass',
|
||||
name: 'test'
|
||||
}
|
||||
const testUser = await testUtils.createUser(userObj)
|
||||
// console.log('TestUser: ', testUser)
|
||||
|
||||
context.user = testUser.user
|
||||
context.token = testUser.token
|
||||
})
|
||||
|
||||
describe('POST /auth', () => {
|
||||
it('should throw 401 if credentials are incorrect', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: `${LOCALHOST}/auth`,
|
||||
data: {
|
||||
email: 'test@test.com',
|
||||
password: 'wrongpassword'
|
||||
}
|
||||
}
|
||||
|
||||
const result = await axios(options)
|
||||
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
console.log(
|
||||
`result stringified: ${JSON.stringify(result.data, null, 2)}`
|
||||
)
|
||||
assert(false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
assert(err.response.status === 401, 'Error code 401 expected.')
|
||||
const userObj = {
|
||||
email: 'test@test.com',
|
||||
password: 'pass',
|
||||
name: 'test'
|
||||
}
|
||||
const testUser = await testUtils.createUser(userObj)
|
||||
// console.log('TestUser: ', testUser)
|
||||
|
||||
context.user = testUser.user
|
||||
context.token = testUser.token
|
||||
})
|
||||
|
||||
it('should throw 401 if email is wrong format', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: `${LOCALHOST}/auth`,
|
||||
data: {
|
||||
email: 'wrongEmail',
|
||||
password: 'wrongpassword'
|
||||
describe('POST /auth', () => {
|
||||
it('should throw 401 if credentials are incorrect', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: `${LOCALHOST}/auth`,
|
||||
data: {
|
||||
email: 'test@test.com',
|
||||
password: 'wrongpassword'
|
||||
}
|
||||
}
|
||||
|
||||
const result = await axios(options)
|
||||
|
||||
// console.log(`result: ${JSON.stringify(result, null, 2)}`)
|
||||
|
||||
console.log(
|
||||
`result stringified: ${JSON.stringify(result.data, null, 2)}`
|
||||
)
|
||||
assert(false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
assert(err.response.status === 401, 'Error code 401 expected.')
|
||||
}
|
||||
})
|
||||
|
||||
await axios(options)
|
||||
assert(false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
assert(err.response.status === 401, 'Error code 401 expected.')
|
||||
}
|
||||
})
|
||||
|
||||
it('should auth user', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: `${LOCALHOST}/auth`,
|
||||
data: {
|
||||
email: 'test@test.com',
|
||||
password: 'pass'
|
||||
it('should throw 401 if email is wrong format', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: `${LOCALHOST}/auth`,
|
||||
data: {
|
||||
email: 'wrongEmail',
|
||||
password: 'wrongpassword'
|
||||
}
|
||||
}
|
||||
}
|
||||
const result = await axios(options)
|
||||
// console.log(`result: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert(result.status === 200, 'Status Code 200 expected.')
|
||||
assert(
|
||||
result.data.user.email === 'test@test.com',
|
||||
'Email of test expected'
|
||||
)
|
||||
assert(
|
||||
result.data.user.password === undefined,
|
||||
'Password expected to be omited'
|
||||
)
|
||||
} catch (err) {
|
||||
console.log(
|
||||
'Error authenticating test user: ' + JSON.stringify(err, null, 2)
|
||||
)
|
||||
throw err
|
||||
}
|
||||
await axios(options)
|
||||
assert(false, 'Unexpected result')
|
||||
} catch (err) {
|
||||
assert(err.response.status === 401, 'Error code 401 expected.')
|
||||
}
|
||||
})
|
||||
|
||||
it('should auth user', async () => {
|
||||
try {
|
||||
const options = {
|
||||
method: 'post',
|
||||
url: `${LOCALHOST}/auth`,
|
||||
data: {
|
||||
email: 'test@test.com',
|
||||
password: 'pass'
|
||||
}
|
||||
}
|
||||
const result = await axios(options)
|
||||
// console.log(`result: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert(result.status === 200, 'Status Code 200 expected.')
|
||||
assert(
|
||||
result.data.user.email === 'test@test.com',
|
||||
'Email of test expected'
|
||||
)
|
||||
assert(
|
||||
result.data.user.password === undefined,
|
||||
'Password expected to be omited'
|
||||
)
|
||||
} catch (err) {
|
||||
console.log(
|
||||
'Error authenticating test user: ' + JSON.stringify(err, null, 2)
|
||||
)
|
||||
throw err
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,15 +1,15 @@
|
||||
const config = require('../../../config')
|
||||
const axios = require('axios').default
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
import config from '../../../config/index.js'
|
||||
import axios from 'axios'
|
||||
import { assert } from 'chai'
|
||||
import sinon from 'sinon'
|
||||
|
||||
import { context as mockContext } from '../../unit/mocks/ctx-mock.js'
|
||||
import ContactController from '../../../src/controllers/rest-api/contact/controller.js'
|
||||
|
||||
// Mock data
|
||||
// const mockData = require('./mocks/contact-mocks')
|
||||
|
||||
const LOCALHOST = `http://localhost:${config.port}`
|
||||
|
||||
const mockContext = require('../../unit/mocks/ctx-mock').context
|
||||
const ContactController = require('../../../src/controllers/rest-api/contact/controller')
|
||||
let uut
|
||||
let sandbox
|
||||
|
||||
|
||||
@@ -1,17 +1,15 @@
|
||||
const config = require('../../../config')
|
||||
const assert = require('chai').assert
|
||||
import config from '../../../config/index.js'
|
||||
import { assert } from 'chai'
|
||||
import axios from 'axios'
|
||||
import sinon from 'sinon'
|
||||
import util from 'util'
|
||||
|
||||
const axios = require('axios').default
|
||||
const sinon = require('sinon')
|
||||
|
||||
const util = require('util')
|
||||
import LogsController from '../../../src/controllers/rest-api/logs/controller.js'
|
||||
import { context as mockContext } from '../../unit/mocks/ctx-mock.js'
|
||||
util.inspect.defaultOptions = { depth: 1 }
|
||||
|
||||
const LOCALHOST = `http://localhost:${config.port}`
|
||||
|
||||
const LogsController = require('../../../src/controllers/rest-api/logs/controller')
|
||||
const mockContext = require('../../unit/mocks/ctx-mock').context
|
||||
|
||||
let sandbox
|
||||
let uut
|
||||
describe('LogsApi', () => {
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user