mirror of
https://github.com/Permissionless-Software-Foundation/ipfs-bch-wallet-service.git
synced 2026-09-22 09:12:02 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e91dd7f834 | ||
|
|
e717124fc7 | ||
|
|
3c8d2cfdd3 | ||
|
|
71fbebe672 | ||
|
|
cab819081a | ||
|
|
4f0adbb773 | ||
|
|
e04cb1f05a | ||
|
|
6f619db666 | ||
|
|
126bee86bd | ||
|
|
3761d139f1 | ||
|
|
006727cc9d | ||
|
|
bcd2a54aa7 | ||
|
|
70155c7b90 | ||
|
|
7035e1777b | ||
|
|
ed82c1102e |
+9
-3
@@ -54,7 +54,7 @@ async function startServer () {
|
||||
|
||||
// Attach REST API and JSON RPC controllers to the app.
|
||||
const controllers = require('../src/controllers')
|
||||
controllers.attachControllers(app)
|
||||
await controllers.attachControllers(app)
|
||||
|
||||
// Enable CORS for testing
|
||||
// THIS IS A SECURITY RISK. COMMENT OUT FOR PRODUCTION
|
||||
@@ -69,8 +69,14 @@ async function startServer () {
|
||||
console.log(`Server started on ${config.port}`)
|
||||
|
||||
// Create the system admin user.
|
||||
const success = await adminLib.createSystemUser()
|
||||
if (success) console.log('System admin user created.')
|
||||
try {
|
||||
const success = await adminLib.createSystemUser()
|
||||
if (success) console.log('System admin user created.')
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
'Error trying to create system admin. Perhaps one already exists?'
|
||||
)
|
||||
}
|
||||
|
||||
return app
|
||||
}
|
||||
|
||||
Vendored
+17
-1
@@ -23,6 +23,18 @@ module.exports = {
|
||||
? process.env.EMAILPASS
|
||||
: 'emailpassword',
|
||||
|
||||
// FullStack.cash account information, used for automatic JWT handling.
|
||||
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',
|
||||
FULLSTACKPASS: process.env.FULLSTACKPASS ? process.env.FULLSTACKPASS : 'demo',
|
||||
|
||||
// IPFS settings.
|
||||
isCircuitRelay: process.env.ENABLE_CIRCUIT_RELAY ? true : false,
|
||||
|
||||
@@ -42,5 +54,9 @@ module.exports = {
|
||||
name: 'Permissionless Software Foundation',
|
||||
url: 'https://PSFoundation.cash'
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
// IPFS Ports
|
||||
ipfsTcpPort: process.env.IPFS_TCP_PORT ? process.env.IPFS_TCP_PORT : 5668,
|
||||
ipfsWsPort: process.env.IPFS_WS_PORT ? process.env.IPFS_WS_PORT : 5669
|
||||
}
|
||||
|
||||
+13
-10
@@ -1,25 +1,28 @@
|
||||
# Start the testnet server with the command 'docker-compose up -d'
|
||||
|
||||
koa-mongodb:
|
||||
bch-wallet-mongodb:
|
||||
image: mongo
|
||||
container_name: mongo-koa
|
||||
container_name: mongo-bch-service
|
||||
ports:
|
||||
- "5555:27017" # <host port>:<container port>
|
||||
- '5704:27017' # <host port>:<container port>
|
||||
volumes:
|
||||
- ./database:/data/db
|
||||
command: mongod --smallfiles --logpath=/dev/null # -- quiet
|
||||
command: mongod --logpath=/dev/null # -- quiet
|
||||
restart: always
|
||||
|
||||
koa:
|
||||
bch-wallet-service:
|
||||
build: ./production/
|
||||
dockerfile: Dockerfile
|
||||
container_name: koa
|
||||
container_name: bch-wallet-service
|
||||
links:
|
||||
- koa-mongodb
|
||||
- bch-wallet-mongodb
|
||||
ports:
|
||||
- "5001:5001" # <host port>:<container port>
|
||||
# <host port>:<container port>
|
||||
- '5700:5700' # REST API
|
||||
- '5701:5701' # IPFS TCP Port
|
||||
- '5702:5702' # IPFS WS Port
|
||||
volumes:
|
||||
# - ./logs:/home/coinjoin/consolidating-coinjoin/logs
|
||||
- ./keys:/home/safeuser/keys
|
||||
- ./ipfsdata:/home/safeuser/ipfsdata
|
||||
- ./orbitdb:/home/safeuser/orbitdb
|
||||
|
||||
restart: always
|
||||
|
||||
Generated
+414
-488
File diff suppressed because it is too large
Load Diff
+4
-3
@@ -8,7 +8,7 @@
|
||||
"test": "npm run test:all",
|
||||
"test:all": "export SVC_ENV=test && nyc --reporter=text mocha --exit --timeout 15000 --recursive test/unit test/e2e/automated/",
|
||||
"test:unit": "export SVC_ENV=test && mocha --exit --timeout 15000 --recursive test/unit/",
|
||||
"test:e2e:auto": "export SVC_ENV=test && mocha --exit --timeout 15000 test/e2e/automated/",
|
||||
"test:e2e:auto": "export SVC_ENV=test && mocha --exit --timeout 30000 test/e2e/automated/",
|
||||
"test:temp": "export SVC_ENV=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",
|
||||
@@ -23,14 +23,15 @@
|
||||
},
|
||||
"repository": "Permissionless-Software-Foundation/ipfs-bch-wallet-service",
|
||||
"dependencies": {
|
||||
"@psf/bch-js": "^4.20.1",
|
||||
"@psf/bch-js": "^4.20.2",
|
||||
"axios": "^0.21.1",
|
||||
"bcryptjs": "^2.4.3",
|
||||
"glob": "^7.1.6",
|
||||
"ipfs": "^0.54.4",
|
||||
"ipfs-coord": "^3.2.0",
|
||||
"ipfs-coord": "^4.0.1",
|
||||
"jsonrpc-lite": "^2.2.0",
|
||||
"jsonwebtoken": "^8.5.1",
|
||||
"jwt-bch-lib": "^1.3.0",
|
||||
"kcors": "^2.2.2",
|
||||
"koa": "^2.13.1",
|
||||
"koa-bodyparser": "^4.3.0",
|
||||
|
||||
@@ -29,13 +29,13 @@ RUN echo 'abcd8765' | sudo -S pwd
|
||||
|
||||
# Clone the rest.bitcoin.com repository
|
||||
WORKDIR /home/safeuser
|
||||
RUN git clone https://github.com/christroutner/koa-api-boilerplate
|
||||
RUN mv koa-api-boilerplate koa
|
||||
RUN mkdir keys
|
||||
RUN git clone https://github.com/Permissionless-Software-Foundation/ipfs-bch-wallet-service
|
||||
# RUN mv koa-api-boilerplate koa
|
||||
# RUN mkdir keys
|
||||
|
||||
# Switch to the desired branch. `master` is usually stable,
|
||||
# and `stage` has the most up-to-date changes.
|
||||
WORKDIR /home/safeuser/koa
|
||||
WORKDIR /home/safeuser/ipfs-bch-wallet-service
|
||||
|
||||
# For development: switch to unstable branch
|
||||
# RUN git checkout unstable
|
||||
@@ -46,10 +46,11 @@ RUN npm install
|
||||
# Generate the API docs
|
||||
RUN npm run docs
|
||||
|
||||
VOLUME /home/safeuser/keys
|
||||
|
||||
# Expose the port the API will be served on.
|
||||
EXPOSE 5001
|
||||
# Calling out the directorys that should be mounted as external volumes.
|
||||
RUN mkdir /home/safeuser/ipfsdata
|
||||
RUN mkdir /home/safeuser/orbitdb
|
||||
VOLUME /home/safeuser/ipfsdata
|
||||
VOLUME /home/safeuser/orbitdb
|
||||
|
||||
# Start the application.
|
||||
COPY start-production start-production
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Remove all untagged docker images.
|
||||
docker rmi $(docker images | grep "^<none>" | awk '{print $3}')
|
||||
|
||||
@@ -1,3 +1,18 @@
|
||||
#!/bin/bash
|
||||
export KOA_ENV=production
|
||||
|
||||
# Customize these environment variables for your own installation.
|
||||
export AUTHSERVER=https://auth.fullstack.cash
|
||||
export APISERVER=https://api.fullstack.cash/v5/
|
||||
export FULLSTACKLOGIN=bch-ipfs@bchtest.net
|
||||
export FULLSTACKPASS=bchoveripfs
|
||||
|
||||
# Ports
|
||||
export PORT=5700 # REST API
|
||||
export IPFS_TCP_PORT=5701 # IPSF TCP Port
|
||||
export IPFS_WS_PORT=5701 # IPFS WS Port
|
||||
|
||||
export ENABLE_CIRCUIT_RELAY=1
|
||||
|
||||
export SVC_ENV=production
|
||||
npm start
|
||||
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
/*
|
||||
A library of utility functions for working with FullStack.cash JWT tokens.
|
||||
|
||||
Feel free to copy this library into your own app, as well as the unit tests
|
||||
for this file.
|
||||
*/
|
||||
|
||||
const JwtLib = require('jwt-bch-lib')
|
||||
const BCHJS = require('@psf/bch-js')
|
||||
|
||||
class FullStackJWT {
|
||||
constructor (localConfig = {}) {
|
||||
// Input Validation
|
||||
this.authServer = localConfig.authServer
|
||||
if (!this.authServer || typeof this.authServer !== 'string') {
|
||||
throw new Error(
|
||||
'Must pass a url for the AUTH server when instantiating FullStackJWT class.'
|
||||
)
|
||||
}
|
||||
this.apiServer = localConfig.apiServer
|
||||
if (!this.apiServer || typeof this.apiServer !== 'string') {
|
||||
throw new Error(
|
||||
'Must pass a url for the API server when instantiating FullStackJWT class.'
|
||||
)
|
||||
}
|
||||
this.login = localConfig.login
|
||||
if (!this.login || typeof this.login !== 'string') {
|
||||
throw new Error(
|
||||
'Must pass a FullStack.cash login (email) instantiating FullStackJWT class.'
|
||||
)
|
||||
}
|
||||
this.password = localConfig.password
|
||||
if (!this.password || typeof this.password !== 'string') {
|
||||
throw new Error(
|
||||
'Must pass a FullStack.cash account password when instantiating FullStackJWT class.'
|
||||
)
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.jwtLib = new JwtLib({
|
||||
// Overwrite default values with the values in the config file.
|
||||
server: this.server,
|
||||
login: this.login,
|
||||
password: this.password
|
||||
})
|
||||
|
||||
// State
|
||||
this.apiToken = '' // Default value.
|
||||
this.bchjs = {}
|
||||
}
|
||||
|
||||
// Get's a JWT token from FullStack.cash.
|
||||
async getJWT () {
|
||||
try {
|
||||
// Skip connecting FullStack.cash auth server to the network if this is an E2E test.
|
||||
if (process.env.E2ETEST) {
|
||||
this.apiToken = 'faketoken'
|
||||
return this.apiToken
|
||||
}
|
||||
|
||||
// Log into the auth server.
|
||||
await this.jwtLib.register()
|
||||
|
||||
this.apiToken = this.jwtLib.userData.apiToken
|
||||
if (!this.apiToken) {
|
||||
throw new Error('This account does not have a JWT')
|
||||
}
|
||||
console.log(`Retrieved JWT token: ${this.apiToken}\n`)
|
||||
|
||||
// Ensure the JWT token is valid to use.
|
||||
const isValid = await this.jwtLib.validateApiToken()
|
||||
|
||||
// Get a new token with the same API level, if the existing token is not
|
||||
// valid (probably expired).
|
||||
if (!isValid.isValid) {
|
||||
this.apiToken = await this.jwtLib.getApiToken(
|
||||
this.jwtLib.userData.apiLevel
|
||||
)
|
||||
console.log(
|
||||
`The JWT token was not valid. Retrieved new JWT token: ${this.apiToken}\n`
|
||||
)
|
||||
} else {
|
||||
console.log('JWT token is valid.\n')
|
||||
}
|
||||
|
||||
return this.apiToken
|
||||
} catch (err) {
|
||||
console.error(
|
||||
`Error trying to log into ${this.server} and retrieve JWT token.`
|
||||
)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Create an instance of bchjs with the validated JWT token. Returns this
|
||||
// instance of bch-js.
|
||||
instanceBchjs () {
|
||||
this.bchjs = new BCHJS({
|
||||
restURL: this.apiServer,
|
||||
apiToken: this.apiToken
|
||||
})
|
||||
|
||||
return this.bchjs
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = FullStackJWT
|
||||
+16
-1
@@ -4,6 +4,9 @@
|
||||
https://troutsblog.com/blog/clean-architecture
|
||||
*/
|
||||
|
||||
// Load the config file
|
||||
const config = require('../../config')
|
||||
|
||||
// Load individual adapter libraries.
|
||||
const IPFSAdapter = require('./ipfs')
|
||||
const LocalDB = require('./localdb')
|
||||
@@ -12,6 +15,7 @@ const Passport = require('./passport')
|
||||
const Nodemailer = require('./nodemailer')
|
||||
const { wlogger } = require('./wlogger')
|
||||
const JSONFiles = require('./json-files')
|
||||
const FullStackJWT = require('./fullstack-jwt')
|
||||
|
||||
// Instantiate adapter libraries.
|
||||
const ipfs = new IPFSAdapter()
|
||||
@@ -21,6 +25,15 @@ const passport = new Passport()
|
||||
const nodemailer = new Nodemailer()
|
||||
const jsonFiles = new JSONFiles()
|
||||
|
||||
// Get a valid JWT API key and instance bch-js.
|
||||
const fullStackJwt = new FullStackJWT({
|
||||
authServer: config.AUTHSERVER,
|
||||
apiServer: config.APISERVER,
|
||||
login: config.FULLSTACKLOGIN,
|
||||
password: config.FULLSTACKPASS
|
||||
})
|
||||
const bchjs = {} // Placeholder.
|
||||
|
||||
module.exports = {
|
||||
ipfs,
|
||||
localdb,
|
||||
@@ -28,5 +41,7 @@ module.exports = {
|
||||
passport,
|
||||
nodemailer,
|
||||
wlogger,
|
||||
jsonFiles
|
||||
jsonFiles,
|
||||
fullStackJwt,
|
||||
bchjs
|
||||
}
|
||||
|
||||
@@ -19,8 +19,15 @@ class IPFS {
|
||||
|
||||
// Provides a global start() function that triggers the start() function in
|
||||
// the underlying libraries.
|
||||
async start () {
|
||||
async start (localConfig = {}) {
|
||||
try {
|
||||
const bchjs = localConfig.bchjs
|
||||
if (!bchjs) {
|
||||
throw new Error(
|
||||
'Instance of bch-js must be passed when instantiating IPFS adapter.'
|
||||
)
|
||||
}
|
||||
|
||||
// Start IPFS
|
||||
await this.ipfsAdapter.start()
|
||||
console.log('IPFS is ready.')
|
||||
@@ -30,7 +37,8 @@ class IPFS {
|
||||
|
||||
// Start ipfs-coord
|
||||
this.ipfsCoordAdapter = new this.IpfsCoordAdapter({
|
||||
ipfs: this.ipfs
|
||||
ipfs: this.ipfs,
|
||||
bchjs
|
||||
})
|
||||
await this.ipfsCoordAdapter.start()
|
||||
console.log('ipfs-coord is ready.')
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
// Global npm libraries
|
||||
const IpfsCoord = require('ipfs-coord')
|
||||
const BCHJS = require('@psf/bch-js')
|
||||
// const BCHJS = require('@psf/bch-js')
|
||||
|
||||
// Local libraries
|
||||
const config = require('../../../config')
|
||||
@@ -23,11 +23,16 @@ class IpfsCoordAdapter {
|
||||
'Instance of IPFS must be passed when instantiating ipfs-coord.'
|
||||
)
|
||||
}
|
||||
this.bchjs = localConfig.bchjs
|
||||
if (!this.bchjs) {
|
||||
throw new Error(
|
||||
'Instance of bch-js must be passed when instantiating ipfs-coord.'
|
||||
)
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.IpfsCoord = IpfsCoord
|
||||
this.ipfsCoord = {}
|
||||
this.bchjs = new BCHJS()
|
||||
// this.rpc = new JSONRPC()
|
||||
this.config = config
|
||||
|
||||
@@ -37,7 +42,7 @@ class IpfsCoordAdapter {
|
||||
_this = this
|
||||
}
|
||||
|
||||
async start () {
|
||||
async start (localConfig = {}) {
|
||||
this.ipfsCoord = new this.IpfsCoord({
|
||||
ipfs: this.ipfs,
|
||||
type: 'node.js',
|
||||
@@ -49,8 +54,12 @@ class IpfsCoordAdapter {
|
||||
announceJsonLd: this.config.announceJsonLd
|
||||
})
|
||||
|
||||
// Wait for the ipfs-coord library to signal that it is ready.
|
||||
await this.ipfsCoord.isReady()
|
||||
// Skip connecting ipfs-coord to the network if this is an E2E test.
|
||||
if (!process.env.E2ETEST) {
|
||||
// Wait for the ipfs-coord library to signal that it is ready.
|
||||
await this.ipfsCoord.ipfs.start()
|
||||
await this.ipfsCoord.isReady()
|
||||
}
|
||||
|
||||
// Signal that this adapter is ready.
|
||||
this.isReady = true
|
||||
|
||||
@@ -63,6 +63,7 @@ class IpfsAdapter {
|
||||
|
||||
// Stop the IPFS node if we're running tests.
|
||||
if (this.config.env === 'test') {
|
||||
console.log('Stopping IPFS for tests.')
|
||||
await this.ipfs.stop()
|
||||
}
|
||||
|
||||
|
||||
@@ -22,13 +22,24 @@ const RESTControllers = require('./rest-api')
|
||||
// Top-level function for this library.
|
||||
// Start the various Controllers and attach them to the app.
|
||||
async function attachControllers (app) {
|
||||
// Attach the REST controllers to the Koa app.
|
||||
attachRESTControllers(app)
|
||||
try {
|
||||
// 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 adapters.fullStackJwt.getJWT()
|
||||
// Instantiate bch-js with the JWT token, and overwrite the placeholder for bch-js.
|
||||
adapters.bchjs = await adapters.fullStackJwt.instanceBchjs()
|
||||
|
||||
// Start IPFS.
|
||||
await adapters.ipfs.start()
|
||||
// Attach the REST controllers to the Koa app.
|
||||
attachRESTControllers(app)
|
||||
|
||||
attachRPCControllers()
|
||||
// Start IPFS.
|
||||
await adapters.ipfs.start({ bchjs: adapters.bchjs })
|
||||
|
||||
attachRPCControllers()
|
||||
} catch (err) {
|
||||
console.error('Error in attachControllers()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
function attachRESTControllers (app) {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
// Public npm libraries
|
||||
const jsonrpc = require('jsonrpc-lite')
|
||||
const BCHJS = require('@psf/bch-js')
|
||||
// const BCHJS = require('@psf/bch-js')
|
||||
|
||||
// Local libraries
|
||||
// const UserLib = require('../../../use-cases/user')
|
||||
@@ -32,7 +32,8 @@ class BCHRPC {
|
||||
this.jsonrpc = jsonrpc
|
||||
this.validators = new Validators(localConfig)
|
||||
this.rateLimit = new RateLimit()
|
||||
this.bchjs = new BCHJS()
|
||||
// this.bchjs = new BCHJS()
|
||||
this.bchjs = this.adapters.bchjs
|
||||
}
|
||||
|
||||
// Top-level router for this library. All other methods in this class are for
|
||||
@@ -44,7 +45,7 @@ class BCHRPC {
|
||||
// console.log('fulcrumRouter rpcData: ', rpcData)
|
||||
|
||||
endpoint = rpcData.payload.params.endpoint
|
||||
let user
|
||||
// let user
|
||||
|
||||
// Route the call based on the value of the method property.
|
||||
switch (endpoint) {
|
||||
@@ -59,21 +60,14 @@ class BCHRPC {
|
||||
case 'utxos':
|
||||
await this.rateLimit.limiter(rpcData.from)
|
||||
return await this.utxos(rpcData)
|
||||
//
|
||||
// case 'getUser':
|
||||
// user = await this.validators.ensureUser(rpcData)
|
||||
// await this.rateLimit.limiter(rpcData.from)
|
||||
// return await this.getUser(rpcData, user)
|
||||
//
|
||||
// case 'updateUser':
|
||||
// user = await this.validators.ensureTargetUserOrAdmin(rpcData)
|
||||
// await this.rateLimit.limiter(rpcData.from)
|
||||
// return await this.updateUser(rpcData, user)
|
||||
//
|
||||
// case 'deleteUser':
|
||||
// user = await this.validators.ensureTargetUserOrAdmin(rpcData)
|
||||
// await this.rateLimit.limiter(rpcData.from)
|
||||
// return await this.deleteUser(rpcData, user)
|
||||
|
||||
case 'broadcast':
|
||||
await this.rateLimit.limiter(rpcData.from)
|
||||
return await this.broadcast(rpcData)
|
||||
|
||||
case 'transaction':
|
||||
await this.rateLimit.limiter(rpcData.from)
|
||||
return await this.transaction(rpcData)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error in BCHRPC/rpcRouter()')
|
||||
@@ -204,7 +198,82 @@ class BCHRPC {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO create deleteUser()
|
||||
/**
|
||||
* @api {JSON} /bch Broadcast
|
||||
* @apiPermission public
|
||||
* @apiName Broadcast
|
||||
* @apiGroup JSON BCH
|
||||
* @apiDescription Broadcast a transaction to the BCH network.
|
||||
* The transaction should be encoded as a hexidecimal string.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "broadcast", "hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000"}}
|
||||
*
|
||||
*/
|
||||
async broadcast (rpcData) {
|
||||
try {
|
||||
// console.log('createUser rpcData: ', rpcData)
|
||||
|
||||
const hex = rpcData.payload.params.hex
|
||||
|
||||
const data = await this.bchjs.RawTransactions.sendRawTransaction(hex)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
const retObj = data
|
||||
retObj.status = 200
|
||||
|
||||
return retObj
|
||||
} catch (err) {
|
||||
// console.error('Error in createUser()')
|
||||
// throw err
|
||||
|
||||
// Return an error response
|
||||
return {
|
||||
success: false,
|
||||
status: 422,
|
||||
message: err.message,
|
||||
endpoint: 'broadcast'
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {JSON} /bch Transaction
|
||||
* @apiPermission public
|
||||
* @apiName Transaction
|
||||
* @apiGroup JSON BCH
|
||||
* @apiDescription Get data about a specific transaction.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "transaction", "txid": "01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b"}}
|
||||
*
|
||||
*/
|
||||
async transaction (rpcData) {
|
||||
try {
|
||||
// console.log('transaction rpcData: ', rpcData)
|
||||
|
||||
const txid = rpcData.payload.params.txid
|
||||
|
||||
const data = await this.bchjs.Transaction.get(txid.toString())
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
const retObj = data
|
||||
retObj.status = 200
|
||||
|
||||
return retObj
|
||||
} catch (err) {
|
||||
// console.error('Error in createUser()')
|
||||
// throw err
|
||||
|
||||
// Return an error response
|
||||
return {
|
||||
success: false,
|
||||
status: 422,
|
||||
message: err.message,
|
||||
endpoint: 'transaction'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BCHRPC
|
||||
|
||||
@@ -14,7 +14,7 @@ class RateLimit {
|
||||
// Set default rate limit options.
|
||||
this.defaultOptions = {
|
||||
interval: { min: 1 },
|
||||
max: 1000, // 1000 RPM while prototyping.
|
||||
max: 600, // 600 RPM while prototyping.
|
||||
onLimitReached: this.onLimitReached
|
||||
}
|
||||
|
||||
@@ -48,7 +48,7 @@ class RateLimit {
|
||||
onLimitReached () {
|
||||
try {
|
||||
const error = new Error() // Establish provided options as the default options.
|
||||
error.message = 'Too many requests, please try again later.'
|
||||
error.message = 'Too many requests, please slow down your requests.'
|
||||
error.status = 429
|
||||
throw error
|
||||
} catch (error) {
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
Controller for the /fulcrum REST API endpoints.
|
||||
*/
|
||||
|
||||
const BCHJS = require('@psf/bch-js')
|
||||
// const BCHJS = require('@psf/bch-js')
|
||||
|
||||
let _this
|
||||
|
||||
@@ -22,7 +22,8 @@ class BCHRESTController {
|
||||
)
|
||||
}
|
||||
|
||||
this.bchjs = new BCHJS()
|
||||
// this.bchjs = new BCHJS()
|
||||
this.bchjs = this.adapters.bchjs
|
||||
|
||||
_this = this
|
||||
}
|
||||
@@ -105,8 +106,8 @@ class BCHRESTController {
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /bch/utxos Balance
|
||||
* @apiName Utxos
|
||||
* @api {post} /bch/utxos UTXOs
|
||||
* @apiName UTXOs
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription This endpoint returns UTXOs held at an address, hydrated
|
||||
* with token information.
|
||||
@@ -143,6 +144,83 @@ class BCHRESTController {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /bch/broadcast Broadcast
|
||||
* @apiName Broadcast
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription Broadcast a transaction to the BCH network.
|
||||
* The transaction should be encoded as a hexidecimal string.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000" }' localhost:5001/bch/broadcast
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* success:true,
|
||||
* data: <data>
|
||||
* }
|
||||
*
|
||||
* @apiError UnprocessableEntity Missing required parameters
|
||||
*
|
||||
* @apiErrorExample {json} Error-Response:
|
||||
* HTTP/1.1 422 Unprocessable Entity
|
||||
* {
|
||||
* "status": 422,
|
||||
* "error": "Unprocessable Entity"
|
||||
* }
|
||||
*/
|
||||
async broadcast (ctx) {
|
||||
try {
|
||||
const hex = ctx.request.body.hex
|
||||
|
||||
const txid = await _this.bchjs.RawTransactions.sendRawTransaction(hex)
|
||||
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
|
||||
|
||||
ctx.body = txid
|
||||
} catch (err) {
|
||||
_this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /bch/transaction Transaction
|
||||
* @apiName Transaction
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription Get data about a specific transaction.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "txid": "01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b" }' localhost:5001/bch/transaction
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* success:true,
|
||||
* data: <data>
|
||||
* }
|
||||
*
|
||||
* @apiError UnprocessableEntity Missing required parameters
|
||||
*
|
||||
* @apiErrorExample {json} Error-Response:
|
||||
* HTTP/1.1 422 Unprocessable Entity
|
||||
* {
|
||||
* "status": 422,
|
||||
* "error": "Unprocessable Entity"
|
||||
* }
|
||||
*/
|
||||
async transaction (ctx) {
|
||||
try {
|
||||
const txid = ctx.request.body.txid
|
||||
|
||||
const data = await _this.bchjs.Transaction.get(txid)
|
||||
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
|
||||
|
||||
ctx.body = data
|
||||
} catch (err) {
|
||||
_this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// DRY error handler
|
||||
handleError (ctx, err) {
|
||||
// If an HTTP status is specified by the buisiness logic, use that.
|
||||
|
||||
@@ -48,6 +48,8 @@ class BCHRouter {
|
||||
this.router.post('/transactions', this.bchRESTController.transactions)
|
||||
this.router.post('/balance', this.bchRESTController.balance)
|
||||
this.router.post('/utxos', this.bchRESTController.utxos)
|
||||
this.router.post('/broadcast', this.bchRESTController.broadcast)
|
||||
this.router.post('/transaction', this.bchRESTController.transaction)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(this.router.routes())
|
||||
|
||||
@@ -22,25 +22,31 @@ const LOCALHOST = `http://localhost:${config.port}`
|
||||
|
||||
describe('Auth', () => {
|
||||
before(async () => {
|
||||
// This should be the first instruction. It starts the REST API server.
|
||||
await app.startServer()
|
||||
try {
|
||||
process.env.E2ETEST = 'true'
|
||||
|
||||
// Delete all previous users in the database.
|
||||
await testUtils.deleteAllUsers()
|
||||
// This should be the first instruction. It starts the REST API server.
|
||||
await app.startServer()
|
||||
|
||||
// Create a new admin user.
|
||||
await adminLib.createSystemUser()
|
||||
// Delete all previous users in the database.
|
||||
await testUtils.deleteAllUsers()
|
||||
|
||||
const userObj = {
|
||||
email: 'test@test.com',
|
||||
password: 'pass',
|
||||
name: 'test'
|
||||
// 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
|
||||
} catch (err) {
|
||||
console.log('err in auth beforeAll: ', err)
|
||||
}
|
||||
const testUser = await testUtils.createUser(userObj)
|
||||
// console.log('TestUser: ', testUser)
|
||||
|
||||
context.user = testUser.user
|
||||
context.token = testUser.token
|
||||
})
|
||||
|
||||
describe('POST /auth', () => {
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
Unit tests for the jwt-bch-lib and fullstack-jwt.js adapter library.
|
||||
|
||||
*/
|
||||
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
|
||||
const FullStackJWT = require('../../../src/adapters/fullstack-jwt')
|
||||
|
||||
describe('#FullStackJWT', () => {
|
||||
let sandbox
|
||||
let uut
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
const localConfig = {
|
||||
authServer: 'someserver',
|
||||
apiServer: 'someserver',
|
||||
login: 'somelogin',
|
||||
password: 'somepassword'
|
||||
}
|
||||
uut = new FullStackJWT(localConfig)
|
||||
})
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should throw an error if auth server is not specified', () => {
|
||||
try {
|
||||
uut = new FullStackJWT()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
console.log(uut) // For linting.
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Must pass a url for the AUTH server when instantiating FullStackJWT class.'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if api server is not specified', () => {
|
||||
try {
|
||||
const localConfig = {
|
||||
authServer: 'someserver'
|
||||
}
|
||||
|
||||
uut = new FullStackJWT(localConfig)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
console.log(uut) // For linting.
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Must pass a url for the API server when instantiating FullStackJWT class.'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if login is not specified', () => {
|
||||
try {
|
||||
const localConfig = {
|
||||
authServer: 'someserver',
|
||||
apiServer: 'someserver'
|
||||
}
|
||||
uut = new FullStackJWT(localConfig)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
console.log(uut) // For linting.
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Must pass a FullStack.cash login (email) instantiating FullStackJWT class.'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if login is not specified', () => {
|
||||
try {
|
||||
const localConfig = {
|
||||
authServer: 'someserver',
|
||||
apiServer: 'someserver',
|
||||
login: 'somelogin'
|
||||
}
|
||||
uut = new FullStackJWT(localConfig)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
console.log(uut) // For linting.
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Must pass a FullStack.cash account password when instantiating FullStackJWT class.'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getJWT', () => {
|
||||
it('should return the JWT token', async () => {
|
||||
// Mock dependencies to force a code path.
|
||||
sandbox.stub(uut.jwtLib, 'register').resolves({})
|
||||
uut.jwtLib.userData.apiToken = 'abc123'
|
||||
sandbox.stub(uut.jwtLib, 'validateApiToken').resolves({ isValid: true })
|
||||
|
||||
const result = await uut.getJWT()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result, 'abc123')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox.stub(uut.jwtLib, 'register').rejects(new Error('test error'))
|
||||
|
||||
await uut.getJWT()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if user does not have a JWT', async () => {
|
||||
try {
|
||||
// Mock dependencies to force a code path.
|
||||
sandbox.stub(uut.jwtLib, 'register').resolves({})
|
||||
|
||||
await uut.getJWT()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('err.message: ', err.message)
|
||||
assert.include(err.message, 'This account does not have a JWT')
|
||||
}
|
||||
})
|
||||
|
||||
it('should retrieve a new JWT token if the old one invalid', async () => {
|
||||
// Mock dependencies to force a code path.
|
||||
sandbox.stub(uut.jwtLib, 'register').resolves({})
|
||||
uut.jwtLib.userData.apiToken = 'abc123'
|
||||
uut.jwtLib.userData.apiLevel = 30
|
||||
sandbox.stub(uut.jwtLib, 'validateApiToken').resolves({ isValid: false })
|
||||
sandbox.stub(uut.jwtLib, 'getApiToken').resolves('xyz789')
|
||||
|
||||
const result = await uut.getJWT()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result, 'xyz789')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#instanceBchjs', () => {
|
||||
it('should return an instance of bch-js', () => {
|
||||
const result = uut.instanceBchjs()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'restURL')
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -15,7 +15,8 @@ describe('#IPFS', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
const ipfs = IPFSMock.create()
|
||||
uut = new IPFSCoordAdapter({ ipfs })
|
||||
const bchjs = {}
|
||||
uut = new IPFSCoordAdapter({ ipfs, bchjs })
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
@@ -35,6 +36,20 @@ describe('#IPFS', () => {
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if bchjs instance is not included', () => {
|
||||
try {
|
||||
const ipfs = IPFSMock.create()
|
||||
uut = new IPFSCoordAdapter({ ipfs })
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Instance of bch-js must be passed when instantiating ipfs-coord.'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#start', () => {
|
||||
|
||||
@@ -22,12 +22,26 @@ describe('#IPFS-adapter-index', () => {
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#start', () => {
|
||||
it('should throw error if bch-js is not passed', async () => {
|
||||
try {
|
||||
await uut.start()
|
||||
|
||||
assert.fail('Unexpected code path.')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.include(
|
||||
err.message,
|
||||
'Instance of bch-js must be passed when instantiating IPFS adapter.'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should return a promise that resolves into an instance of IPFS.', async () => {
|
||||
// Mock dependencies.
|
||||
uut.ipfsAdapter = new IPFSMock()
|
||||
uut.IpfsCoordAdapter = IPFSCoordMock
|
||||
|
||||
const result = await uut.start()
|
||||
const result = await uut.start({ bchjs: {} })
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
@@ -37,7 +51,7 @@ describe('#IPFS-adapter-index', () => {
|
||||
// Force an error
|
||||
sandbox.stub(uut.ipfsAdapter, 'start').rejects(new Error('test error'))
|
||||
|
||||
await uut.start()
|
||||
await uut.start({ bchjs: {} })
|
||||
|
||||
assert.fail('Unexpected code path.')
|
||||
} catch (err) {
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
// const assert = require('chai').assert
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
|
||||
const adapters = require('../../../src/adapters')
|
||||
@@ -23,6 +23,8 @@ describe('#Controllers', () => {
|
||||
it('should attach the controllers', async () => {
|
||||
// mock IPFS
|
||||
sandbox.stub(adapters.ipfs, 'start').resolves({})
|
||||
sandbox.stub(adapters.fullStackJwt, 'getJWT').resolves({})
|
||||
sandbox.stub(adapters.fullStackJwt, 'instanceBchjs').resolves({})
|
||||
adapters.ipfs.ipfsCoordAdapter = {
|
||||
attachRPCRouter: () => {}
|
||||
}
|
||||
@@ -33,5 +35,19 @@ describe('#Controllers', () => {
|
||||
|
||||
await attachControllers(app)
|
||||
})
|
||||
|
||||
it('should catch and throw errors', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(adapters.fullStackJwt, 'getJWT')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
await attachControllers()
|
||||
} catch (err) {
|
||||
// console.log('err.message: ', err.message)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -69,7 +69,7 @@ describe('#rate-limit', () => {
|
||||
assert.equal(error.status, 429)
|
||||
assert.include(
|
||||
error.message,
|
||||
'Too many requests, please try again later.'
|
||||
'Too many requests, please slow down your requests.'
|
||||
)
|
||||
}
|
||||
})
|
||||
@@ -101,7 +101,7 @@ describe('#rate-limit', () => {
|
||||
} catch (error) {
|
||||
assert.include(
|
||||
error.message,
|
||||
'Too many requests, please try again later.'
|
||||
'Too many requests, please slow down your requests.'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -118,6 +118,44 @@ describe('#BCHRPC', () => {
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should route to the broadcast method', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut, 'broadcast').resolves(true)
|
||||
|
||||
// Generate the parsed data that the main router would pass to this
|
||||
// endpoint.
|
||||
const id = uid()
|
||||
const rpcCall = jsonrpc.request(id, 'bch', {
|
||||
endpoint: 'broadcast'
|
||||
})
|
||||
const jsonStr = JSON.stringify(rpcCall, null, 2)
|
||||
const rpcData = jsonrpc.parse(jsonStr)
|
||||
rpcData.from = 'Origin request'
|
||||
|
||||
const result = await uut.bchRouter(rpcData)
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should route to the transaction method', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut, 'transaction').resolves(true)
|
||||
|
||||
// Generate the parsed data that the main router would pass to this
|
||||
// endpoint.
|
||||
const id = uid()
|
||||
const rpcCall = jsonrpc.request(id, 'bch', {
|
||||
endpoint: 'transaction'
|
||||
})
|
||||
const jsonStr = JSON.stringify(rpcCall, null, 2)
|
||||
const rpcData = jsonrpc.parse(jsonStr)
|
||||
rpcData.from = 'Origin request'
|
||||
|
||||
const result = await uut.bchRouter(rpcData)
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should return 500 status on routing issue', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut, 'transactions').rejects(new Error('test error'))
|
||||
@@ -284,4 +322,102 @@ describe('#BCHRPC', () => {
|
||||
assert.equal(response.endpoint, 'utxos')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#broadcast', () => {
|
||||
it('should return data from bchjs', async () => {
|
||||
// Mock dependencies
|
||||
sandbox
|
||||
.stub(uut.bchjs.RawTransactions, 'sendRawTransaction')
|
||||
.resolves({ success: true })
|
||||
|
||||
// Generate the parsed data that the main router would pass to this
|
||||
// endpoint.
|
||||
const id = uid()
|
||||
const rpcCall = jsonrpc.request(id, 'bch', {
|
||||
endpoint: 'broadcast',
|
||||
hex: 'testData'
|
||||
})
|
||||
const jsonStr = JSON.stringify(rpcCall, null, 2)
|
||||
const rpcData = jsonrpc.parse(jsonStr)
|
||||
|
||||
const response = await uut.broadcast(rpcData)
|
||||
// console.log('response: ', response)
|
||||
|
||||
assert.equal(response.success, true)
|
||||
assert.equal(response.status, 200)
|
||||
})
|
||||
|
||||
it('should return an error for invalid input', async () => {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.bchjs.RawTransactions, 'sendRawTransaction')
|
||||
.rejects(new Error('Invalid data'))
|
||||
|
||||
// Generate the parsed data that the main router would pass to this
|
||||
// endpoint.
|
||||
const id = uid()
|
||||
const rpcCall = jsonrpc.request(id, 'bch', {
|
||||
endpoint: 'broadcast',
|
||||
hex: 'testHex'
|
||||
})
|
||||
const jsonStr = JSON.stringify(rpcCall, null, 2)
|
||||
const rpcData = jsonrpc.parse(jsonStr)
|
||||
|
||||
const response = await uut.broadcast(rpcData)
|
||||
// console.log('response: ', response)
|
||||
|
||||
assert.equal(response.success, false)
|
||||
assert.equal(response.status, 422)
|
||||
assert.equal(response.message, 'Invalid data')
|
||||
assert.equal(response.endpoint, 'broadcast')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#transaction', () => {
|
||||
it('should return data from bchjs', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.bchjs.Transaction, 'get').resolves({ success: true })
|
||||
|
||||
// Generate the parsed data that the main router would pass to this
|
||||
// endpoint.
|
||||
const id = uid()
|
||||
const rpcCall = jsonrpc.request(id, 'bch', {
|
||||
endpoint: 'transaction',
|
||||
txid: 'testData'
|
||||
})
|
||||
const jsonStr = JSON.stringify(rpcCall, null, 2)
|
||||
const rpcData = jsonrpc.parse(jsonStr)
|
||||
|
||||
const response = await uut.transaction(rpcData)
|
||||
// console.log('response: ', response)
|
||||
|
||||
assert.equal(response.success, true)
|
||||
assert.equal(response.status, 200)
|
||||
})
|
||||
|
||||
it('should return an error for invalid input', async () => {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.bchjs.Transaction, 'get')
|
||||
.rejects(new Error('Invalid data'))
|
||||
|
||||
// Generate the parsed data that the main router would pass to this
|
||||
// endpoint.
|
||||
const id = uid()
|
||||
const rpcCall = jsonrpc.request(id, 'bch', {
|
||||
endpoint: 'transaction',
|
||||
txid: 'testTxid'
|
||||
})
|
||||
const jsonStr = JSON.stringify(rpcCall, null, 2)
|
||||
const rpcData = jsonrpc.parse(jsonStr)
|
||||
|
||||
const response = await uut.transaction(rpcData)
|
||||
// console.log('response: ', response)
|
||||
|
||||
assert.equal(response.success, false)
|
||||
assert.equal(response.status, 422)
|
||||
assert.equal(response.message, 'Invalid data')
|
||||
assert.equal(response.endpoint, 'transaction')
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -163,6 +163,76 @@ describe('#BCH-REST-Router', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#broadcast', () => {
|
||||
it('should return data from bchjs', async () => {
|
||||
// Mock dependencies
|
||||
sandbox
|
||||
.stub(uut.bchjs.RawTransactions, 'sendRawTransaction')
|
||||
.resolves({ success: true })
|
||||
|
||||
ctx.request.body = {
|
||||
hex: 'testData'
|
||||
}
|
||||
|
||||
await uut.broadcast(ctx)
|
||||
// console.log('ctx.body: ', ctx.body)
|
||||
|
||||
assert.equal(ctx.body.success, true)
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.bchjs.RawTransactions, 'sendRawTransaction')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
ctx.request.body = {
|
||||
hex: 'testData'
|
||||
}
|
||||
|
||||
await uut.broadcast(ctx)
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#transaction', () => {
|
||||
it('should return data from bchjs', async () => {
|
||||
// Mock dependencies
|
||||
sandbox.stub(uut.bchjs.Transaction, 'get').resolves({ success: true })
|
||||
|
||||
ctx.request.body = {
|
||||
txid: 'testData'
|
||||
}
|
||||
|
||||
await uut.transaction(ctx)
|
||||
// console.log('ctx.body: ', ctx.body)
|
||||
|
||||
assert.equal(ctx.body.success, true)
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.bchjs.Transaction, 'get')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
ctx.request.body = {
|
||||
txid: 'testData'
|
||||
}
|
||||
|
||||
await uut.transaction(ctx)
|
||||
} catch (err) {
|
||||
// console.log('err: ', err)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#handleError', () => {
|
||||
it('should pass an error message', () => {
|
||||
try {
|
||||
|
||||
@@ -47,4 +47,20 @@ const localdb = {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { ipfs, localdb }
|
||||
const bchjs = {
|
||||
Electrumx: {
|
||||
transactions: () => {},
|
||||
balance: () => {}
|
||||
},
|
||||
Utxo: {
|
||||
get: () => {}
|
||||
},
|
||||
Transaction: {
|
||||
get: () => {}
|
||||
},
|
||||
RawTransactions: {
|
||||
sendRawTransaction: () => {}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { ipfs, localdb, bchjs }
|
||||
|
||||
@@ -3,6 +3,12 @@
|
||||
*/
|
||||
|
||||
class IPFSCoord {
|
||||
constructor () {
|
||||
this.ipfs = {
|
||||
async start () {}
|
||||
}
|
||||
}
|
||||
|
||||
async isReady () {
|
||||
return true
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user