mirror of
https://github.com/Permissionless-Software-Foundation/ipfs-bch-wallet-consumer.git
synced 2026-09-22 01:02:04 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bb44b75973 | ||
|
|
698cca555b | ||
|
|
76b3740e74 | ||
|
|
2622038df5 | ||
|
|
f1fbe34b58 | ||
|
|
d232b6404d | ||
|
|
9f92cef122 | ||
|
|
18e98f9a3b | ||
|
|
938cc755f1 | ||
|
|
6db6db8187 | ||
|
|
35af9c08fa | ||
|
|
2caf18977e | ||
|
|
8857242570 | ||
|
|
4f0e59ea61 | ||
|
|
a1a910c3b5 | ||
|
|
1bf8f756e5 | ||
|
|
1e513cc133 | ||
|
|
2f48f1bb61 | ||
|
|
70c81af88e | ||
|
|
cdbb8b18b6 | ||
|
|
b3c3bb4ae2 | ||
|
|
4d291f7310 | ||
|
|
8641a4100c | ||
|
|
3a30dd6b03 | ||
|
|
762ed8dd43 | ||
|
|
17914f2937 | ||
|
|
aa5b921dee | ||
|
|
717744094e | ||
|
|
123754f6ea | ||
|
|
766826d86e | ||
|
|
74c6bc5737 | ||
|
|
bac6c8b1e9 | ||
|
|
9e7d955bc4 |
@@ -7,6 +7,7 @@
|
||||
#logs
|
||||
logs/*.json
|
||||
*.log
|
||||
*.log.*
|
||||
npm-debug.log*
|
||||
|
||||
# Runtime data
|
||||
@@ -65,5 +66,6 @@ system-user-*.json
|
||||
orbitdb
|
||||
ipfsdata
|
||||
.ipfsdata
|
||||
run-dev.sh
|
||||
|
||||
!README.md
|
||||
|
||||
Vendored
+14
-1
@@ -78,5 +78,18 @@ module.exports = {
|
||||
// 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) : 2
|
||||
debugLevel: process.env.DEBUG_LEVEL ? parseInt(process.env.DEBUG_LEVEL) : 2,
|
||||
|
||||
// Preferred provider running ipfs-bch-wallet-service for BCH wallet.
|
||||
preferredProvider: process.env.PREFERRED_PROVIDER
|
||||
? process.env.PREFERRED_PROVIDER
|
||||
: '',
|
||||
// : 'QmdBGB8S6sEr19gaAxJYjhRbz5ZmMiH5a1JyyMmaxcRKnY'
|
||||
// : 'QmUTx6KqYKVZbKpKxR7vGDUgZFYVvVVyEWDeCYq4GwBCff'
|
||||
// Used for development
|
||||
|
||||
// Preferred P2WDB provider
|
||||
preferredP2wdbProvider: process.env.PREFERRED_P2WDB_PROVIDER
|
||||
? process.env.PREFERRED_P2WDB_PROVIDER
|
||||
: ''
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -7,6 +7,6 @@
|
||||
module.exports = {
|
||||
session: 'secret-boilerplate-token',
|
||||
token: 'secret-jwt-token',
|
||||
database: 'mongodb://localhost:27017/ipfs-service-dev',
|
||||
database: 'mongodb://localhost:27017/ipfs-bch-consumer-dev',
|
||||
env: 'dev'
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -13,6 +13,6 @@ module.exports = {
|
||||
// database: 'mongodb://172.17.0.1:5555/ipfs-service-prod',
|
||||
database: process.env.DBURL
|
||||
? process.env.DBURL
|
||||
: 'mongodb://172.17.0.1:5555/ipfs-service-prod',
|
||||
: 'mongodb://172.17.0.1:5555/ipfs-bch-consumer-prod',
|
||||
env: 'prod'
|
||||
}
|
||||
|
||||
Vendored
+1
-1
@@ -7,6 +7,6 @@
|
||||
module.exports = {
|
||||
session: 'secret-boilerplate-token',
|
||||
token: 'secret-jwt-token',
|
||||
database: 'mongodb://localhost:27017/ipfs-service-test',
|
||||
database: 'mongodb://localhost:27017/ipfs-bch-consumer-test',
|
||||
env: 'test'
|
||||
}
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
const common = require('./env/common')
|
||||
|
||||
const env = process.env.SVC_ENV || 'development'
|
||||
const env = process.env.CONSUMER_ENV || 'development'
|
||||
const config = require(`./env/${env}`)
|
||||
|
||||
module.exports = Object.assign({}, common, config)
|
||||
|
||||
Generated
+7
-7
@@ -14,7 +14,7 @@
|
||||
"axios": "0.21.1",
|
||||
"bcryptjs": "2.4.3",
|
||||
"glob": "7.1.6",
|
||||
"ipfs-coord": "6.7.4",
|
||||
"ipfs-coord": "6.7.5",
|
||||
"jsonrpc-lite": "2.2.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"jwt-bch-lib": "1.3.0",
|
||||
@@ -9611,9 +9611,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ipfs-coord": {
|
||||
"version": "6.7.4",
|
||||
"resolved": "http://94.130.170.209:4873/ipfs-coord/-/ipfs-coord-6.7.4.tgz",
|
||||
"integrity": "sha512-nboEp4xge4ZE8VWSCPhIB5PXQafKChacLOTVwxFfmjzgrXenacw3PBuR/UGxMJ3JX+t13ee/M662Jd8nzp7rZA==",
|
||||
"version": "6.7.5",
|
||||
"resolved": "http://94.130.170.209:4873/ipfs-coord/-/ipfs-coord-6.7.5.tgz",
|
||||
"integrity": "sha512-CwsitrjROTV0YF+H6pasR+K2AcWyJMhOLMdLLmm8e6a6kL3NShUUcOQ8lgLiPLM8qbfQF/B+cPaXW0aVyOxqWg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "0.21.4",
|
||||
@@ -33139,9 +33139,9 @@
|
||||
}
|
||||
},
|
||||
"ipfs-coord": {
|
||||
"version": "6.7.4",
|
||||
"resolved": "http://94.130.170.209:4873/ipfs-coord/-/ipfs-coord-6.7.4.tgz",
|
||||
"integrity": "sha512-nboEp4xge4ZE8VWSCPhIB5PXQafKChacLOTVwxFfmjzgrXenacw3PBuR/UGxMJ3JX+t13ee/M662Jd8nzp7rZA==",
|
||||
"version": "6.7.5",
|
||||
"resolved": "http://94.130.170.209:4873/ipfs-coord/-/ipfs-coord-6.7.5.tgz",
|
||||
"integrity": "sha512-CwsitrjROTV0YF+H6pasR+K2AcWyJMhOLMdLLmm8e6a6kL3NShUUcOQ8lgLiPLM8qbfQF/B+cPaXW0aVyOxqWg==",
|
||||
"requires": {
|
||||
"axios": "0.21.4",
|
||||
"bch-encrypt-lib": "2.0.0",
|
||||
|
||||
+6
-6
@@ -6,14 +6,14 @@
|
||||
"scripts": {
|
||||
"start": "node index.js",
|
||||
"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:temp": "export SVC_ENV=test && mocha --exit --timeout 15000 -g '#rate-limit' test/unit/json-rpc/",
|
||||
"test:all": "export CONSUMER_ENV=test && nyc --reporter=text mocha --exit --timeout 15000 --recursive test/unit test/e2e/automated/",
|
||||
"test:unit": "export CONSUMER_ENV=test && mocha --exit --timeout 15000 --recursive test/unit/",
|
||||
"test:e2e:auto": "export CONSUMER_ENV=test && mocha --exit --timeout 15000 test/e2e/automated/",
|
||||
"test:temp": "export CONSUMER_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",
|
||||
"coverage": "nyc report --reporter=text-lcov | coveralls",
|
||||
"coverage:report": "export SVC_ENV=test && nyc --reporter=html mocha --exit --timeout 15000 --recursive test/unit/ test/e2e/automated/"
|
||||
"coverage:report": "export CONSUMER_ENV=test && nyc --reporter=html mocha --exit --timeout 15000 --recursive test/unit/ test/e2e/automated/"
|
||||
},
|
||||
"author": "Chris Troutner <chris.troutner@gmail.com>",
|
||||
"license": "MIT",
|
||||
@@ -28,7 +28,7 @@
|
||||
"axios": "0.21.1",
|
||||
"bcryptjs": "2.4.3",
|
||||
"glob": "7.1.6",
|
||||
"ipfs-coord": "6.7.4",
|
||||
"ipfs-coord": "6.7.5",
|
||||
"jsonrpc-lite": "2.2.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
"jwt-bch-lib": "1.3.0",
|
||||
|
||||
@@ -0,0 +1,430 @@
|
||||
/*
|
||||
This library contains business-logic for dealing with BCH wallet service
|
||||
providers. Most of these functions are called by the /bch REST API endpoints.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const { v4: uid } = require('uuid')
|
||||
const jsonrpc = require('jsonrpc-lite')
|
||||
|
||||
// Local libraries
|
||||
const { wlogger } = require('../wlogger')
|
||||
|
||||
let _this
|
||||
|
||||
class BchAdapter {
|
||||
constructor (localConfig = {}) {
|
||||
// console.log('BCH localConfig: ', localConfig)
|
||||
this.ipfs = localConfig.ipfs
|
||||
if (!this.ipfs) {
|
||||
throw new Error(
|
||||
'An instance of IPFS must be passed when instantiating the BCH Adapter library.'
|
||||
)
|
||||
}
|
||||
this.eventEmitter = localConfig.eventEmitter
|
||||
if (!this.eventEmitter) {
|
||||
throw new Error(
|
||||
'An instance of an EventEmitter must be passed when instantiating the adapters.'
|
||||
)
|
||||
}
|
||||
|
||||
// Connect the RPC handler when the event fires with new data.
|
||||
this.eventEmitter.on('rpcData', this.rpcHandler)
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.uid = uid
|
||||
this.jsonrpc = jsonrpc
|
||||
|
||||
// A queue for holding RPC data that has arrived.
|
||||
this.rpcDataQueue = []
|
||||
|
||||
_this = this // Global handle on instance of this Class.
|
||||
}
|
||||
|
||||
// This handler is triggered when RPC data comes in over IPFS.
|
||||
// Handle RPC input, and add the response to the RPC queue.
|
||||
// Once in the queue, it will get processed by waitForRPCResponse()
|
||||
rpcHandler (data) {
|
||||
try {
|
||||
// Convert string input into an object.
|
||||
// const jsonData = JSON.parse(data)
|
||||
|
||||
console.log(`JSON RPC response for ID ${data.payload.id} received.`)
|
||||
|
||||
_this.rpcDataQueue.push(data)
|
||||
} catch (err) {
|
||||
console.error('Error in rest-api.js/rpcHandler(): ', err)
|
||||
// Do not throw error. This is a top-level function.
|
||||
}
|
||||
}
|
||||
|
||||
// Get the status of BCH wallet services this node can talk to. Returns an
|
||||
// array of BCH wallet service providers.
|
||||
async getStatus () {
|
||||
try {
|
||||
const peerData = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode.peerData
|
||||
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
|
||||
|
||||
// const status = {
|
||||
// state: this.ipfs.ipfsCoordAdapter.state
|
||||
// }
|
||||
|
||||
// Add names to the IPFS IDs for each provider.
|
||||
const initialServiceProviders =
|
||||
this.ipfs.ipfsCoordAdapter.state.serviceProviders
|
||||
const serviceProviders = []
|
||||
for (let i = 0; i < initialServiceProviders.length; i++) {
|
||||
const thisProvider = initialServiceProviders[i]
|
||||
|
||||
const providerData = peerData.filter((x) => x.from === thisProvider)
|
||||
|
||||
if (providerData.length) {
|
||||
const provObj = {
|
||||
ipfsId: thisProvider,
|
||||
name: providerData[0].data.jsonLd.name
|
||||
}
|
||||
|
||||
serviceProviders.push(provObj)
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(
|
||||
// `serviceProviders: ${JSON.stringify(serviceProviders, null, 2)}`
|
||||
// )
|
||||
|
||||
const outObj = {
|
||||
serviceProviders,
|
||||
selectedProvider:
|
||||
this.ipfs.ipfsCoordAdapter.state.selectedServiceProvider
|
||||
}
|
||||
// console.log('outObj: ', outObj)
|
||||
|
||||
return outObj
|
||||
} catch (err) {
|
||||
// console.log('createUser() error: ', err)
|
||||
wlogger.error('Error in use-cases/bch.js/getStatus()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Choose the BCH wallet service to use.
|
||||
async selectProvider (providerId) {
|
||||
try {
|
||||
this.ipfs.ipfsCoordAdapter.config.preferredProvider = providerId
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
// console.log('createUser() error: ', err)
|
||||
wlogger.error('Error in use-cases/bch.js/getStatus()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get the BCH balance for an array of addresses.
|
||||
async getBalances (addrs) {
|
||||
try {
|
||||
// console.log('addrs: ', addrs)
|
||||
|
||||
// Throw an error if this IPFS node has not yet made a connection to a
|
||||
// wallet service provider.
|
||||
const selectedProvider =
|
||||
this.ipfs.ipfsCoordAdapter.state.selectedServiceProvider
|
||||
if (!selectedProvider) {
|
||||
throw new Error('No BCH Wallet Service provider available yet.')
|
||||
}
|
||||
|
||||
const rpcData = {
|
||||
endpoint: 'balance',
|
||||
addresses: addrs
|
||||
}
|
||||
|
||||
// Generate a UUID for the call.
|
||||
const rpcId = this.uid()
|
||||
|
||||
// Generate a JSON RPC command.
|
||||
const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData)
|
||||
const cmdStr = JSON.stringify(cmd)
|
||||
// console.log('cmdStr: ', cmdStr)
|
||||
|
||||
// Send the RPC command to selected wallet service.
|
||||
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
|
||||
await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage(
|
||||
selectedProvider,
|
||||
cmdStr,
|
||||
thisNode
|
||||
)
|
||||
|
||||
// Wait for data to come back from the wallet service.
|
||||
const data = await this.waitForRPCResponse(rpcId)
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
// console.log('createUser() error: ', err)
|
||||
wlogger.error('Error in use-cases/bch.js/getBalances()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async getUtxos (addr) {
|
||||
try {
|
||||
// Throw an error if this IPFS node has not yet made a connection to a
|
||||
// wallet service provider.
|
||||
const selectedProvider =
|
||||
this.ipfs.ipfsCoordAdapter.state.selectedServiceProvider
|
||||
if (!selectedProvider) {
|
||||
throw new Error('No BCH Wallet Service provider available yet.')
|
||||
}
|
||||
|
||||
const rpcData = {
|
||||
endpoint: 'utxos',
|
||||
address: addr
|
||||
}
|
||||
|
||||
// Generate a UUID for the call.
|
||||
const rpcId = this.uid()
|
||||
|
||||
// Generate a JSON RPC command.
|
||||
const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData)
|
||||
const cmdStr = JSON.stringify(cmd)
|
||||
// console.log('cmdStr: ', cmdStr)
|
||||
|
||||
// Send the RPC command to selected wallet service.
|
||||
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
|
||||
await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage(
|
||||
selectedProvider,
|
||||
cmdStr,
|
||||
thisNode
|
||||
)
|
||||
|
||||
// Wait for data to come back from the wallet service.
|
||||
const data = await this.waitForRPCResponse(rpcId)
|
||||
|
||||
return data[0]
|
||||
} catch (err) {
|
||||
// console.log('createUser() error: ', err)
|
||||
wlogger.error('Error in use-cases/bch.js/getUtxos()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async broadcast (hex) {
|
||||
try {
|
||||
// Throw an error if this IPFS node has not yet made a connection to a
|
||||
// wallet service provider.
|
||||
const selectedProvider =
|
||||
this.ipfs.ipfsCoordAdapter.state.selectedServiceProvider
|
||||
if (!selectedProvider) {
|
||||
throw new Error('No BCH Wallet Service provider available yet.')
|
||||
}
|
||||
|
||||
const rpcData = {
|
||||
endpoint: 'broadcast',
|
||||
hex
|
||||
}
|
||||
|
||||
// Generate a UUID for the call.
|
||||
const rpcId = this.uid()
|
||||
|
||||
// Generate a JSON RPC command.
|
||||
const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData)
|
||||
const cmdStr = JSON.stringify(cmd)
|
||||
// console.log('cmdStr: ', cmdStr)
|
||||
|
||||
// Send the RPC command to selected wallet service.
|
||||
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
|
||||
await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage(
|
||||
selectedProvider,
|
||||
cmdStr,
|
||||
thisNode
|
||||
)
|
||||
|
||||
// Wait for data to come back from the wallet service.
|
||||
const data = await this.waitForRPCResponse(rpcId)
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
// console.log('createUser() error: ', err)
|
||||
wlogger.error('Error in adapters/bch.js/broadcast()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async getTransactions (address) {
|
||||
try {
|
||||
// Throw an error if this IPFS node has not yet made a connection to a
|
||||
// wallet service provider.
|
||||
const selectedProvider =
|
||||
this.ipfs.ipfsCoordAdapter.state.selectedServiceProvider
|
||||
if (!selectedProvider) {
|
||||
throw new Error('No BCH Wallet Service provider available yet.')
|
||||
}
|
||||
|
||||
const rpcData = {
|
||||
endpoint: 'transactions',
|
||||
addresses: [address]
|
||||
}
|
||||
|
||||
// Generate a UUID for the call.
|
||||
const rpcId = this.uid()
|
||||
|
||||
// Generate a JSON RPC command.
|
||||
const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData)
|
||||
const cmdStr = JSON.stringify(cmd)
|
||||
// console.log('cmdStr: ', cmdStr)
|
||||
|
||||
// Send the RPC command to selected wallet service.
|
||||
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
|
||||
await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage(
|
||||
selectedProvider,
|
||||
cmdStr,
|
||||
thisNode
|
||||
)
|
||||
|
||||
// Wait for data to come back from the wallet service.
|
||||
const data = await this.waitForRPCResponse(rpcId)
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
// console.log('createUser() error: ', err)
|
||||
wlogger.error('Error in adapters/bch.js/transactions()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async getTransaction (txid) {
|
||||
try {
|
||||
// Throw an error if this IPFS node has not yet made a connection to a
|
||||
// wallet service provider.
|
||||
const selectedProvider =
|
||||
this.ipfs.ipfsCoordAdapter.state.selectedServiceProvider
|
||||
if (!selectedProvider) {
|
||||
throw new Error('No BCH Wallet Service provider available yet.')
|
||||
}
|
||||
|
||||
const rpcData = {
|
||||
endpoint: 'transaction',
|
||||
txid
|
||||
}
|
||||
|
||||
// Generate a UUID for the call.
|
||||
const rpcId = this.uid()
|
||||
|
||||
// Generate a JSON RPC command.
|
||||
const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData)
|
||||
const cmdStr = JSON.stringify(cmd)
|
||||
// console.log('cmdStr: ', cmdStr)
|
||||
|
||||
// Send the RPC command to selected wallet service.
|
||||
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
|
||||
await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage(
|
||||
selectedProvider,
|
||||
cmdStr,
|
||||
thisNode
|
||||
)
|
||||
|
||||
// Wait for data to come back from the wallet service.
|
||||
const data = await this.waitForRPCResponse(rpcId)
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
// console.log('createUser() error: ', err)
|
||||
wlogger.error('Error in adapters/bch.js/transaction()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async getPubKey (address) {
|
||||
try {
|
||||
// Throw an error if this IPFS node has not yet made a connection to a
|
||||
// wallet service provider.
|
||||
const selectedProvider =
|
||||
this.ipfs.ipfsCoordAdapter.state.selectedServiceProvider
|
||||
if (!selectedProvider) {
|
||||
throw new Error('No BCH Wallet Service provider available yet.')
|
||||
}
|
||||
|
||||
const rpcData = {
|
||||
endpoint: 'pubkey',
|
||||
address
|
||||
}
|
||||
|
||||
// Generate a UUID for the call.
|
||||
const rpcId = this.uid()
|
||||
|
||||
// Generate a JSON RPC command.
|
||||
const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData)
|
||||
const cmdStr = JSON.stringify(cmd)
|
||||
console.log('cmdStr: ', cmdStr)
|
||||
|
||||
// Send the RPC command to selected wallet service.
|
||||
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
|
||||
await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage(
|
||||
selectedProvider,
|
||||
cmdStr,
|
||||
thisNode
|
||||
)
|
||||
|
||||
// Wait for data to come back from the wallet service.
|
||||
const data = await this.waitForRPCResponse(rpcId)
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
console.log('getPubKey() error: ', err)
|
||||
wlogger.error('Error in adapters/bch.js/getPubKey()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a promise that resolves to data when the RPC response is recieved.
|
||||
async waitForRPCResponse (rpcId) {
|
||||
try {
|
||||
// Initialize variables for tracking the return data.
|
||||
let dataFound = false
|
||||
let cnt = 0
|
||||
|
||||
// Default return value, if the remote computer does not respond in time.
|
||||
let data = {
|
||||
success: false,
|
||||
message: 'request timed out',
|
||||
data: ''
|
||||
}
|
||||
|
||||
// Loop that waits for a response from the service provider.
|
||||
do {
|
||||
// console.log(`this.rpcDataQueue.length: ${this.rpcDataQueue.length}`)
|
||||
for (let i = 0; i < this.rpcDataQueue.length; i++) {
|
||||
const rawData = this.rpcDataQueue[i]
|
||||
// console.log(`rawData: ${JSON.stringify(rawData, null, 2)}`)
|
||||
|
||||
if (rawData.payload.id === rpcId) {
|
||||
dataFound = true
|
||||
// console.log('data was found in the queue')
|
||||
|
||||
data = rawData.payload.result.value
|
||||
|
||||
// Remove the data from the queue
|
||||
this.rpcDataQueue.splice(i, 1)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Wait between loops.
|
||||
// await this.sleep(1000)
|
||||
await this.ipfs.ipfsCoordAdapter.bchjs.Util.sleep(2000)
|
||||
|
||||
cnt++
|
||||
|
||||
// Exit if data was returned, or the window for a response expires.
|
||||
} while (!dataFound && cnt < 10)
|
||||
// console.log(`dataFound: ${dataFound}, cnt: ${cnt}`)
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
console.error('Error in waitForRPCResponse()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BchAdapter
|
||||
@@ -16,6 +16,8 @@ const Nodemailer = require('./nodemailer')
|
||||
// const { wlogger } = require('./wlogger')
|
||||
const JSONFiles = require('./json-files')
|
||||
const FullStackJWT = require('./fullstack-jwt')
|
||||
const BCH = require('./bch')
|
||||
const P2WDB = require('./p2wdb')
|
||||
|
||||
const config = require('../../config')
|
||||
|
||||
@@ -31,6 +33,7 @@ class Adapters {
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.ipfs = new IPFSAdapter(localConfig)
|
||||
localConfig.ipfs = this.ipfs
|
||||
this.localdb = new LocalDB()
|
||||
this.logapi = new LogsAPI()
|
||||
this.passport = new Passport()
|
||||
@@ -38,6 +41,8 @@ class Adapters {
|
||||
this.jsonFiles = new JSONFiles()
|
||||
this.bchjs = new BCHJS()
|
||||
this.config = config
|
||||
this.bch = new BCH(localConfig)
|
||||
this.p2wdb = new P2WDB(localConfig)
|
||||
|
||||
// Get a valid JWT API key and instance bch-js.
|
||||
this.fullStackJwt = new FullStackJWT(config)
|
||||
|
||||
@@ -58,6 +58,130 @@ class IPFS {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get the status of this IPFS node.
|
||||
getStatus () {
|
||||
try {
|
||||
// console.log(
|
||||
// 'this.ipfsCoordAdapter.ipfsCoord.thisNode: ',
|
||||
// this.ipfsCoordAdapter.ipfsCoord.thisNode
|
||||
// )
|
||||
|
||||
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)
|
||||
|
||||
// Loop through each IPFS peer and hydrate it with data from the peerData.
|
||||
for (let i = 0; i < ipfsPeers.length; i++) {
|
||||
const thisPeer = 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]
|
||||
|
||||
// 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
|
||||
|
||||
if (showAll) {
|
||||
// Add all the peer data.
|
||||
thisPeer.peerData = thisPeerData
|
||||
}
|
||||
} catch (err) {
|
||||
console.log(
|
||||
`Error trying to hydrate peer ${thisPeer.peer}: ${err.message}`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return ipfsPeers
|
||||
} catch (err) {
|
||||
console.error('Error in getPeers(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// 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 = ''
|
||||
continue
|
||||
}
|
||||
|
||||
thisRelay.name = thisPeer[0].data.jsonLd.name
|
||||
}
|
||||
|
||||
return relayData
|
||||
} catch (err) {
|
||||
console.error('Error in getRelays(): ', err)
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Expects an array of peers and returns an array of peers with duplicates
|
||||
// removed.
|
||||
_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.peer === v.peer) === i)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = IPFS
|
||||
|
||||
@@ -15,8 +15,10 @@ const config = require('../../../config')
|
||||
// const JSONRPC = require('../../controllers/json-rpc/')
|
||||
|
||||
// The minimum version of ipfs-bch-wallet-service that this wallet can work with.
|
||||
const MIN_BCH_WALLET_VERSION = '1.11.0'
|
||||
const MIN_BCH_WALLET_VERSION = '1.11.11'
|
||||
const WALLET_PROTOCOL = 'bch-wallet'
|
||||
const MIN_P2WDB_VERSION = '1.4.0'
|
||||
const P2WDB_PROTOCOL = 'p2wdb'
|
||||
|
||||
let _this
|
||||
|
||||
@@ -48,12 +50,15 @@ class IpfsCoordAdapter {
|
||||
this.isReady = false
|
||||
|
||||
// Periodically poll services for available wallet service providers.
|
||||
this.pollServiceInterval = setInterval(this.pollForServices, 10000)
|
||||
this.pollBchServiceInterval = setInterval(this.pollForBchServices, 10000)
|
||||
this.pollP2wdbServiceInterval = setInterval(this.pollForP2wdbServices, 11000)
|
||||
|
||||
// State object. TODO: Make this more robust.
|
||||
this.state = {
|
||||
serviceProviders: [],
|
||||
selectedServiceProvider: ''
|
||||
selectedServiceProvider: '',
|
||||
p2wdbProviders: [],
|
||||
selectedP2wdbProvider: ''
|
||||
}
|
||||
|
||||
_this = this
|
||||
@@ -112,9 +117,84 @@ class IpfsCoordAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
// Poll the ipfs-coord coordination channel for available service providers.
|
||||
// This method is called periodically by a timer-interval.
|
||||
pollForServices () {
|
||||
// Poll the ipfs-coord coordination channel for available P2WDB service
|
||||
// providers. This method is called periodically by a timer-interval.
|
||||
pollForP2wdbServices () {
|
||||
try {
|
||||
// An array of IPFS IDs of other nodes in the coordination pubsub channel.
|
||||
const peers = _this.ipfsCoord.thisNode.peerList
|
||||
// console.log(`peers: ${JSON.stringify(peers, null, 2)}`)
|
||||
|
||||
// Array of objects. Each object is the IPFS ID of the peer and contains
|
||||
// data about that peer.
|
||||
const peerData = _this.ipfsCoord.thisNode.peerData
|
||||
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
|
||||
|
||||
for (let i = 0; i < peers.length; i++) {
|
||||
const thisPeer = peers[i]
|
||||
const thisData = peerData.filter((x) => x.from === thisPeer)
|
||||
const thisPeerData = thisData[0]
|
||||
|
||||
// Create a 'fingerprint' that defines the wallet service.
|
||||
const protocol = thisPeerData.data.jsonLd.protocol
|
||||
const version = thisPeerData.data.jsonLd.version
|
||||
// console.log(
|
||||
// `debug: peer ${thisPeer} uses protocol: ${protocol} v${version}`
|
||||
// )
|
||||
|
||||
let versionMatches = false
|
||||
if (version) {
|
||||
versionMatches = _this.semver.gt(version, MIN_P2WDB_VERSION)
|
||||
}
|
||||
|
||||
// p2wdbProviders: [],
|
||||
// selectedP2wdbProvider: ''
|
||||
|
||||
// Ignore any peers that don't match the fingerprint for a BCH wallet
|
||||
// service.
|
||||
if (protocol && protocol.includes(P2WDB_PROTOCOL) && versionMatches) {
|
||||
// console.log('Matching peer: ', thisPeerData)
|
||||
|
||||
// Temporary business logic.
|
||||
// Use the first available wallet service detected.
|
||||
if (_this.state.p2wdbProviders.length === 0) {
|
||||
_this.state.selectedP2wdbProvider = thisPeer
|
||||
|
||||
// Persist the config setting, so it can be used by other commands.
|
||||
// _this.conf.set('selectedService', thisPeer)
|
||||
console.log(`---->P2WDB service selected: ${thisPeer}`)
|
||||
}
|
||||
|
||||
// If a preferred provider is set in the config file, then connect
|
||||
// to the preferred provider when it's discovered.
|
||||
if (
|
||||
_this.config.preferredP2wdbProvider &&
|
||||
thisPeer === _this.config.preferredP2wdbProvider
|
||||
) {
|
||||
_this.state.selectedP2wdbProvider = thisPeer
|
||||
}
|
||||
|
||||
// Check if the peer has already been added to the list of providers.
|
||||
const alreadyExists = _this.state.p2wdbProviders.filter(
|
||||
(x) => x === thisPeer
|
||||
)
|
||||
|
||||
// Add the peer to the list of serviceProviders if it doesn't already
|
||||
// exist in the list.
|
||||
if (!alreadyExists.length) {
|
||||
_this.state.p2wdbProviders.push(thisPeer)
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error in pollForP2wdbServices()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Poll the ipfs-coord coordination channel for available BCH wallet service
|
||||
// providers. This method is called periodically by a timer-interval.
|
||||
pollForBchServices () {
|
||||
try {
|
||||
// An array of IPFS IDs of other nodes in the coordination pubsub channel.
|
||||
const peers = _this.ipfsCoord.thisNode.peerList
|
||||
@@ -157,6 +237,16 @@ class IpfsCoordAdapter {
|
||||
console.log(`---->BCH wallet service selected: ${thisPeer}`)
|
||||
}
|
||||
|
||||
// If a preferred provider is set in the config file, then connect
|
||||
// to the preferred provider when it's discovered.
|
||||
if (
|
||||
_this.config.preferredProvider &&
|
||||
thisPeer === _this.config.preferredProvider
|
||||
) {
|
||||
_this.state.selectedServiceProvider = thisPeer
|
||||
}
|
||||
|
||||
// Check if the peer has already been added to the list of providers.
|
||||
const alreadyExists = _this.state.serviceProviders.filter(
|
||||
(x) => x === thisPeer
|
||||
)
|
||||
@@ -169,7 +259,7 @@ class IpfsCoordAdapter {
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error in pollForServices(): ', err)
|
||||
console.error('Error in pollForBchServices(): ', err)
|
||||
// Do not throw error. This is a top-level function.
|
||||
}
|
||||
}
|
||||
@@ -188,14 +278,4 @@ class IpfsCoordAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
// Create a random number to use in the name of this IPFS n ode.
|
||||
// const randNum = Math.floor(Math.random() * 10000)
|
||||
|
||||
// const announceJsonLd = {
|
||||
// '@context': 'https://schema.org/',
|
||||
// '@type': 'Person',
|
||||
// name: `wallet-consumer-${randNum}`,
|
||||
// description: 'A consumer of BCH wallet services'
|
||||
// }
|
||||
|
||||
module.exports = IpfsCoordAdapter
|
||||
|
||||
@@ -11,10 +11,13 @@
|
||||
// Global npm libraries
|
||||
// const IPFS = require('ipfs')
|
||||
const IPFS = require('@chris.troutner/ipfs')
|
||||
const fs = require('fs')
|
||||
|
||||
// Local libraries
|
||||
const config = require('../../../config')
|
||||
|
||||
const IPFS_DIR = './.ipfsdata/ipfs'
|
||||
|
||||
class IpfsAdapter {
|
||||
constructor (localConfig) {
|
||||
// Encapsulate dependencies
|
||||
@@ -23,6 +26,7 @@ class IpfsAdapter {
|
||||
// Properties of this class instance.
|
||||
this.isReady = false
|
||||
this.config = config
|
||||
this.fs = fs
|
||||
}
|
||||
|
||||
// Start an IPFS node.
|
||||
@@ -30,7 +34,7 @@ class IpfsAdapter {
|
||||
try {
|
||||
// Ipfs Options
|
||||
const ipfsOptions = {
|
||||
repo: './.ipfsdata/ipfs',
|
||||
repo: IPFS_DIR,
|
||||
start: true,
|
||||
config: {
|
||||
relay: {
|
||||
@@ -51,6 +55,11 @@ class IpfsAdapter {
|
||||
`/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'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -71,6 +80,12 @@ class IpfsAdapter {
|
||||
return this.ipfs
|
||||
} catch (err) {
|
||||
console.error('Error in ipfs.js/start()')
|
||||
|
||||
// If IPFS crashes because the /blocks directory is full, wipe the directory.
|
||||
if (err.message.includes('No space left on device')) {
|
||||
this.rmBlocksDir()
|
||||
}
|
||||
|
||||
throw err
|
||||
}
|
||||
}
|
||||
@@ -80,6 +95,26 @@ class IpfsAdapter {
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = IpfsAdapter
|
||||
|
||||
@@ -0,0 +1,268 @@
|
||||
/*
|
||||
This library contains business-logic for dealing with P2WDB service
|
||||
providers. Most of these functions are called by the /p2wdb REST API endpoints.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const { v4: uid } = require('uuid')
|
||||
const jsonrpc = require('jsonrpc-lite')
|
||||
|
||||
// Local libraries
|
||||
const { wlogger } = require('../wlogger')
|
||||
|
||||
let _this
|
||||
|
||||
class P2wdbAdapter {
|
||||
constructor (localConfig = {}) {
|
||||
// console.log('BCH localConfig: ', localConfig)
|
||||
this.ipfs = localConfig.ipfs
|
||||
if (!this.ipfs) {
|
||||
throw new Error(
|
||||
'An instance of IPFS must be passed when instantiating the P2WDB Adapter library.'
|
||||
)
|
||||
}
|
||||
this.eventEmitter = localConfig.eventEmitter
|
||||
if (!this.eventEmitter) {
|
||||
throw new Error(
|
||||
'An instance of an EventEmitter must be passed when instantiating the adapters.'
|
||||
)
|
||||
}
|
||||
|
||||
// Connect the RPC handler when the event fires with new data.
|
||||
this.eventEmitter.on('rpcData', this.rpcHandler)
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.uid = uid
|
||||
this.jsonrpc = jsonrpc
|
||||
|
||||
// A queue for holding RPC data that has arrived.
|
||||
this.rpcDataQueue = []
|
||||
|
||||
_this = this // Global handle on instance of this Class.
|
||||
}
|
||||
|
||||
// This handler is triggered when RPC data comes in over IPFS.
|
||||
// Handle RPC input, and add the response to the RPC queue.
|
||||
// Once in the queue, it will get processed by waitForRPCResponse()
|
||||
rpcHandler (data) {
|
||||
try {
|
||||
// Convert string input into an object.
|
||||
// const jsonData = JSON.parse(data)
|
||||
|
||||
console.log(`JSON RPC response for ID ${data.payload.id} received.`)
|
||||
|
||||
_this.rpcDataQueue.push(data)
|
||||
} catch (err) {
|
||||
console.error('Error in p2wdb/rpcHandler(): ', err)
|
||||
// Do not throw error. This is a top-level function.
|
||||
}
|
||||
}
|
||||
|
||||
// Get the status of BCH wallet services this node can talk to. Returns an
|
||||
// array of BCH wallet service providers.
|
||||
async getStatus () {
|
||||
try {
|
||||
const peerData = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode.peerData
|
||||
// console.log(`peerData: ${JSON.stringify(peerData, null, 2)}`)
|
||||
|
||||
// const status = {
|
||||
// state: this.ipfs.ipfsCoordAdapter.state
|
||||
// }
|
||||
|
||||
// Add names to the IPFS IDs for each provider.
|
||||
const initialServiceProviders =
|
||||
this.ipfs.ipfsCoordAdapter.state.p2wdbProviders
|
||||
const serviceProviders = []
|
||||
for (let i = 0; i < initialServiceProviders.length; i++) {
|
||||
const thisProvider = initialServiceProviders[i]
|
||||
|
||||
const providerData = peerData.filter((x) => x.from === thisProvider)
|
||||
|
||||
if (providerData.length) {
|
||||
const provObj = {
|
||||
ipfsId: thisProvider,
|
||||
name: providerData[0].data.jsonLd.name
|
||||
}
|
||||
|
||||
serviceProviders.push(provObj)
|
||||
}
|
||||
}
|
||||
|
||||
// console.log(
|
||||
// `serviceProviders: ${JSON.stringify(serviceProviders, null, 2)}`
|
||||
// )
|
||||
|
||||
const outObj = {
|
||||
serviceProviders,
|
||||
selectedProvider:
|
||||
this.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider
|
||||
}
|
||||
// console.log('outObj: ', outObj)
|
||||
|
||||
return outObj
|
||||
} catch (err) {
|
||||
// console.log('createUser() error: ', err)
|
||||
wlogger.error('Error in adapters/p2wdb.js/getStatus()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Choose the BCH wallet service to use.
|
||||
async selectProvider (providerId) {
|
||||
try {
|
||||
this.ipfs.ipfsCoordAdapter.config.preferredP2wdbProvider = providerId
|
||||
|
||||
return true
|
||||
} catch (err) {
|
||||
// console.log('createUser() error: ', err)
|
||||
wlogger.error('Error in adapters/p2wdb/getStatus()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Read an entry from the P2WDB, given an entry hash.
|
||||
async getEntryByHash (hash) {
|
||||
try {
|
||||
// Input validation.
|
||||
if (!hash || typeof hash !== 'string') {
|
||||
throw new Error('getEntry() input hash must be a string.')
|
||||
}
|
||||
|
||||
// Throw an error if this IPFS node has not yet made a connection to a
|
||||
// wallet service provider.
|
||||
const selectedProvider =
|
||||
this.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider
|
||||
if (!selectedProvider) {
|
||||
throw new Error('No P2WDB Service provider available yet.')
|
||||
}
|
||||
|
||||
const rpcData = {
|
||||
endpoint: 'getByHash',
|
||||
hash
|
||||
}
|
||||
|
||||
// Generate a UUID for the call.
|
||||
const rpcId = this.uid()
|
||||
|
||||
// Generate a JSON RPC command.
|
||||
const cmd = this.jsonrpc.request(rpcId, 'p2wdb', rpcData)
|
||||
const cmdStr = JSON.stringify(cmd)
|
||||
// console.log('cmdStr: ', cmdStr)
|
||||
|
||||
// Send the RPC command to selected wallet service.
|
||||
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
|
||||
await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage(
|
||||
selectedProvider,
|
||||
cmdStr,
|
||||
thisNode
|
||||
)
|
||||
|
||||
// Wait for data to come back from the wallet service.
|
||||
const data = await this.waitForRPCResponse(rpcId)
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
console.error('Error in getEntryByHash()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async writeEntry (writeObj) {
|
||||
try {
|
||||
const { txid, signature, message, data } = writeObj
|
||||
|
||||
// Throw an error if this IPFS node has not yet made a connection to a
|
||||
// wallet service provider.
|
||||
const selectedProvider =
|
||||
this.ipfs.ipfsCoordAdapter.state.selectedP2wdbProvider
|
||||
if (!selectedProvider) {
|
||||
throw new Error('No P2WDB Service provider available yet.')
|
||||
}
|
||||
|
||||
const rpcData = {
|
||||
endpoint: 'write',
|
||||
txid,
|
||||
signature,
|
||||
message,
|
||||
data
|
||||
}
|
||||
|
||||
// Generate a UUID for the call.
|
||||
const rpcId = this.uid()
|
||||
|
||||
// Generate a JSON RPC command.
|
||||
const cmd = this.jsonrpc.request(rpcId, 'p2wdb', rpcData)
|
||||
const cmdStr = JSON.stringify(cmd)
|
||||
// console.log('cmdStr: ', cmdStr)
|
||||
|
||||
// Send the RPC command to selected wallet service.
|
||||
const thisNode = this.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
|
||||
await this.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage(
|
||||
selectedProvider,
|
||||
cmdStr,
|
||||
thisNode
|
||||
)
|
||||
|
||||
// Wait for data to come back from the wallet service.
|
||||
const result = await this.waitForRPCResponse(rpcId)
|
||||
|
||||
return result
|
||||
} catch (err) {
|
||||
console.error('Error in adapters/p2wdb/writeEntry()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a promise that resolves to data when the RPC response is recieved.
|
||||
async waitForRPCResponse (rpcId) {
|
||||
try {
|
||||
// Initialize variables for tracking the return data.
|
||||
let dataFound = false
|
||||
let cnt = 0
|
||||
|
||||
// Default return value, if the remote computer does not respond in time.
|
||||
let data = {
|
||||
success: false,
|
||||
message: 'request timed out',
|
||||
data: ''
|
||||
}
|
||||
|
||||
// Loop that waits for a response from the service provider.
|
||||
do {
|
||||
// console.log(`this.rpcDataQueue.length: ${this.rpcDataQueue.length}`)
|
||||
for (let i = 0; i < this.rpcDataQueue.length; i++) {
|
||||
const rawData = this.rpcDataQueue[i]
|
||||
// console.log(`rawData: ${JSON.stringify(rawData, null, 2)}`)
|
||||
|
||||
if (rawData.payload.id === rpcId) {
|
||||
dataFound = true
|
||||
// console.log('data was found in the queue')
|
||||
|
||||
data = rawData.payload.result.value
|
||||
|
||||
// Remove the data from the queue
|
||||
this.rpcDataQueue.splice(i, 1)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Wait between loops.
|
||||
// await this.sleep(1000)
|
||||
await this.ipfs.ipfsCoordAdapter.bchjs.Util.sleep(2000)
|
||||
|
||||
cnt++
|
||||
|
||||
// Exit if data was returned, or the window for a response expires.
|
||||
} while (!dataFound && cnt < 10)
|
||||
// console.log(`dataFound: ${dataFound}, cnt: ${cnt}`)
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
console.error('Error in waitForRPCResponse()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = P2wdbAdapter
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
REST API Controller library for the /user route
|
||||
REST API Controller library for the /bch route
|
||||
*/
|
||||
|
||||
const { wlogger } = require('../../../adapters/wlogger')
|
||||
@@ -41,7 +41,7 @@ class BchRESTControllerLib {
|
||||
*/
|
||||
async getStatus (ctx) {
|
||||
try {
|
||||
const status = await this.useCases.bch.getStatus()
|
||||
const status = await this.adapters.bch.getStatus()
|
||||
|
||||
ctx.body = { status }
|
||||
} catch (err) {
|
||||
@@ -51,6 +51,35 @@ class BchRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /bch/provider Provider
|
||||
* @apiPermission public
|
||||
* @apiName Provider
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription Select a different BCH wallet service on the IPFS network,
|
||||
* for interacting with the BCH blockchain.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "provider": "QmXtHADckCmT6jodpAgn3TcDQWjC29gQd2fKKHDTpo8DJT" }' localhost:5001/bch/provider
|
||||
*/
|
||||
async postProvider (ctx) {
|
||||
try {
|
||||
const providerId = ctx.request.body.provider
|
||||
|
||||
await this.adapters.bch.selectProvider(providerId)
|
||||
|
||||
const body = {
|
||||
success: true
|
||||
}
|
||||
|
||||
ctx.body = body
|
||||
} catch (err) {
|
||||
wlogger.error('Error in bch/controller.js/postProvider(): ')
|
||||
// ctx.throw(422, err.message)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /bch/balance Balance
|
||||
* @apiName Balance
|
||||
@@ -100,7 +129,7 @@ class BchRESTControllerLib {
|
||||
try {
|
||||
const addrs = ctx.request.body.addresses
|
||||
|
||||
const data = await this.useCases.bch.getBalances(addrs)
|
||||
const data = await this.adapters.bch.getBalances(addrs)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
ctx.body = data
|
||||
@@ -109,6 +138,346 @@ class BchRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /bch/utxos UTXOs
|
||||
* @apiName UTXOs
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription This endpoint returns UTXOs held at an address, hydrated
|
||||
* with token information.
|
||||
*
|
||||
* Given an address, this endpoint will return an object with thre following
|
||||
* properties:
|
||||
*
|
||||
* - address: "" - the address these UTXOs are associated with
|
||||
* - bchUtxos: [] - UTXOs confirmed to be spendable as normal BCH
|
||||
* - nullUtxo: [] - UTXOs that did not pass SLP validation. Should be ignored and
|
||||
* not spent, to be safe.
|
||||
* - slpUtxos: {} - UTXOs confirmed to be colored as valid SLP tokens
|
||||
* - type1: {}
|
||||
* - tokens: [] - SLP token Type 1 tokens.
|
||||
* - mintBatons: [] - SLP token Type 1 mint batons.
|
||||
* - nft: {}
|
||||
* - tokens: [] - NFT tokens
|
||||
* - groupTokens: [] - NFT Group tokens, used to create NFT tokens.
|
||||
* - groupMintBatons: [] - Minting baton to create more NFT Group tokens.
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "address": "bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj" }' localhost:5001/bch/utxos
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* [
|
||||
* {
|
||||
* "address":"bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj",
|
||||
* "bchUtxos":[
|
||||
* {
|
||||
* "height":631219,
|
||||
* "tx_hash":"ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f",
|
||||
* "tx_pos":0,
|
||||
* "value":1000,
|
||||
* "txid":"ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f",
|
||||
* "vout":0,
|
||||
* "isValid":false
|
||||
* }
|
||||
* ],
|
||||
* "nullUtxos":[
|
||||
*
|
||||
* ],
|
||||
* "slpUtxos":{
|
||||
* "type1":{
|
||||
* "mintBatons":[
|
||||
*
|
||||
* ],
|
||||
* "tokens":[
|
||||
*
|
||||
* ]
|
||||
* },
|
||||
* "nft":{
|
||||
* "groupMintBatons":[
|
||||
*
|
||||
* ],
|
||||
* "groupTokens":[
|
||||
*
|
||||
* ],
|
||||
* "tokens":[
|
||||
*
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* ]
|
||||
*
|
||||
* @apiError UnprocessableEntity Missing required parameters
|
||||
*
|
||||
* @apiErrorExample {json} Error-Response:
|
||||
* HTTP/1.1 422 Unprocessable Entity
|
||||
* {
|
||||
* "status": 422,
|
||||
* "error": "Unprocessable Entity"
|
||||
* }
|
||||
*/
|
||||
async utxos (ctx) {
|
||||
try {
|
||||
const address = ctx.request.body.address
|
||||
// console.log('address: ', address)
|
||||
|
||||
const utxos = await this.adapters.bch.getUtxos(address)
|
||||
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
|
||||
|
||||
ctx.body = [utxos]
|
||||
} catch (err) {
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @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.
|
||||
*
|
||||
* This endpoint will return a transaction id
|
||||
*
|
||||
* @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
|
||||
* "951299775f68a599b95239bfc385423f87a33e11747c299a22ef9dcf3d1557ec"
|
||||
*
|
||||
* @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.adapters.bch.broadcast(hex)
|
||||
// console.log(`txid: ${JSON.stringify(txid, null, 2)}`)
|
||||
|
||||
ctx.body = txid
|
||||
} catch (err) {
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @api {post} /bch/transactions Transactions
|
||||
* @apiName Transactions
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription This endpoint wraps the bchjs.Electrumx.transactions([]) function.
|
||||
*
|
||||
* Given the 'addresses' property returns an array of objects
|
||||
* with the following properties
|
||||
*
|
||||
* - success : - Petition status
|
||||
* - transactions : [] - Transaction of the provided address
|
||||
* - transactions: [] - Transaction details
|
||||
* - height : - Reference to the blockchain size
|
||||
* - tx_hash: "" - Transaction hash
|
||||
* - address : "" - Address associated to the transactions
|
||||
*
|
||||
* Note: For a single address pass the 'addresses' of string type
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "addresses": ["bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"] }' localhost:5001/bch/transactions
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* HTTP/1.1 200 OK
|
||||
* {
|
||||
* "success":true,
|
||||
* "transactions":[
|
||||
* {
|
||||
* "transactions":[
|
||||
* {
|
||||
* "height":631219,
|
||||
* "tx_hash":"ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f"
|
||||
* }
|
||||
* ],
|
||||
* "address":"bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
*
|
||||
* @apiError UnprocessableEntity Missing required parameters
|
||||
*
|
||||
* @apiErrorExample {json} Error-Response:
|
||||
* HTTP/1.1 422 Unprocessable Entity
|
||||
* {
|
||||
* "status": 422,
|
||||
* "error": "Unprocessable Entity"
|
||||
* }
|
||||
*/
|
||||
async transactions (ctx) {
|
||||
try {
|
||||
const addr = ctx.request.body.address
|
||||
|
||||
const data = await this.adapters.bch.getTransactions(addr)
|
||||
// console.log(`data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
ctx.body = data
|
||||
} catch (err) {
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /bch/transaction Transaction
|
||||
* @apiName Transaction
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription Get data about a specific transaction.
|
||||
* Given a transaction id this endpoint will return an object
|
||||
* with the following properties
|
||||
*
|
||||
* - txid: "" - Transaction ID
|
||||
* - hash: "" - Transaction hash
|
||||
* - version: - Version number
|
||||
* - size: - Transaction size
|
||||
* - locktime: -
|
||||
* - vin: [] - Transaction inputs
|
||||
* - vout: [] - Transaction outputs
|
||||
* - hex: "" - hexadecimal script
|
||||
* - blockhash: "" - Reference to the block register
|
||||
* - confirmations : "" - Transaction confirmations
|
||||
* - time: - Execution time
|
||||
* - blocktime: - Execution time
|
||||
* - isValidSLPTx: - Determines if the transaction was under SLP
|
||||
*
|
||||
* @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
|
||||
* {
|
||||
* "txid":"01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b",
|
||||
* "hash":"01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b",
|
||||
* "version":1,
|
||||
* "size":272,
|
||||
* "locktime":0,
|
||||
* "vin":[
|
||||
* {
|
||||
* "txid":"4deef6de4b973706cd6e8fc8105a41a84be349e4e9717225ee5e7c63538e95e8",
|
||||
* "vout":1,
|
||||
* "scriptSig":{
|
||||
* "asm":"3045022100fce6ef975fa7ec66e0ce0c51d839fd8f56510897252c0b238e7265974bc7c07202200d1d1429154e6775eecdc2829965650bc3ca714a86088d705bd58f8c034f2496[ALL|FORKID] 0467ff2df20f28bc62ad188525868f41d461f7dab3c1e500314cdb5218e5637bfd0f9c02eb5b3f383f698d28ff13547eaf05dd9216130861dd0216824e9d7337e3",
|
||||
* "hex":"483045022100fce6ef975fa7ec66e0ce0c51d839fd8f56510897252c0b238e7265974bc7c07202200d1d1429154e6775eecdc2829965650bc3ca714a86088d705bd58f8c034f249641410467ff2df20f28bc62ad188525868f41d461f7dab3c1e500314cdb5218e5637bfd0f9c02eb5b3f383f698d28ff13547eaf05dd9216130861dd0216824e9d7337e3"
|
||||
* },
|
||||
* "sequence":4294967295,
|
||||
* "address":"bitcoincash:qqrxa0h9jqnc7v4wmj9ysetsp3y7w9l36u8gnnjulq",
|
||||
* "value":0.00001824
|
||||
* }
|
||||
* ],
|
||||
* "vout":[
|
||||
* {
|
||||
* "value":0,
|
||||
* "n":0,
|
||||
* "scriptPubKey":{
|
||||
* "asm":"OP_RETURN -385055325 46226800369048b83cea897639bb39273c8e4b883bd8c2a435bbe7a237cc433a",
|
||||
* "hex":"6a045d7af3962046226800369048b83cea897639bb39273c8e4b883bd8c2a435bbe7a237cc433a",
|
||||
* "type":"nulldata"
|
||||
* }
|
||||
* },
|
||||
* {
|
||||
* "value":0.0000155,
|
||||
* "n":1,
|
||||
* "scriptPubKey":{
|
||||
* "asm":"OP_DUP OP_HASH160 066ebee590278f32aedc8a4865700c49e717f1d7 OP_EQUALVERIFY OP_CHECKSIG",
|
||||
* "hex":"76a914066ebee590278f32aedc8a4865700c49e717f1d788ac",
|
||||
* "reqSigs":1,
|
||||
* "type":"pubkeyhash",
|
||||
* "addresses":[
|
||||
* "bitcoincash:qqrxa0h9jqnc7v4wmj9ysetsp3y7w9l36u8gnnjulq"
|
||||
* ]
|
||||
* }
|
||||
* }
|
||||
* ],
|
||||
* "hex":"0100000001e8958e53637c5eee257271e9e449e34ba8415a10c88f6ecd0637974bdef6ee4d010000008b483045022100fce6ef975fa7ec66e0ce0c51d839fd8f56510897252c0b238e7265974bc7c07202200d1d1429154e6775eecdc2829965650bc3ca714a86088d705bd58f8c034f249641410467ff2df20f28bc62ad188525868f41d461f7dab3c1e500314cdb5218e5637bfd0f9c02eb5b3f383f698d28ff13547eaf05dd9216130861dd0216824e9d7337e3ffffffff020000000000000000276a045d7af3962046226800369048b83cea897639bb39273c8e4b883bd8c2a435bbe7a237cc433a0e060000000000001976a914066ebee590278f32aedc8a4865700c49e717f1d788ac00000000",
|
||||
* "blockhash":"0000000000000000008e8d83cba6d45a9314bc2ef4538d4e0577c6bed8593536",
|
||||
* "confirmations":97988,
|
||||
* "time":1568338904,
|
||||
* "blocktime":1568338904,
|
||||
* "isValidSLPTx":false
|
||||
* }
|
||||
*
|
||||
* @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.adapters.bch.getTransaction(txid)
|
||||
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
|
||||
|
||||
ctx.body = data
|
||||
} catch (err) {
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {REST} /bch/pubkey PubKey
|
||||
* @apiPermission public
|
||||
* @apiName PubKey
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription Get the public key from an address.
|
||||
* Given an address the endpoint will return an object with the
|
||||
* following properties
|
||||
*
|
||||
* - jsonrpc: "" - jsonrpc version
|
||||
* - id: "" - jsonrpc id
|
||||
* - result: {} - Result of the petition with the RPC information
|
||||
* - success: - Request status
|
||||
* - publickey: - Address public key
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "txid": "01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b" }' localhost:5001/bch/pubkey
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* {
|
||||
* "jsonrpc":"2.0",
|
||||
* "id":"555",
|
||||
* "result":{
|
||||
* "method":"bch",
|
||||
* "reciever":"QmU86vLVbUY1UhziKB6rak7GPKRA2QHWvzNm2AjEvXNsT6",
|
||||
* "value":{
|
||||
* "success": true,
|
||||
* "status": 200,
|
||||
* "endpoint": "pubkey",
|
||||
* "pubkey": {
|
||||
* "success": true,
|
||||
* "publicKey": "033f267fec0f7eb2b27f8c2e3052b3d03b09d36b47de4082ffb638ffb334ef0eee"
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
async pubKey (ctx) {
|
||||
try {
|
||||
const address = ctx.request.body.address
|
||||
|
||||
const data = await this.adapters.bch.getPubKey(address)
|
||||
console.log(`pubkey data: ${JSON.stringify(data, 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.
|
||||
|
||||
@@ -52,7 +52,13 @@ class BchRouter {
|
||||
|
||||
// Define the routes and attach the controller.
|
||||
this.router.get('/', this.getStatus)
|
||||
this.router.post('/provider', this.postProvider)
|
||||
this.router.post('/balance', this.postBalance)
|
||||
this.router.post('/utxos', this.postUtxos)
|
||||
this.router.post('/broadcast', this.postBroadcast)
|
||||
this.router.post('/transactions', this.postTransactions)
|
||||
this.router.post('/transaction', this.postTransaction)
|
||||
this.router.post('/pubkey', this.postPubKey)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(this.router.routes())
|
||||
@@ -63,9 +69,33 @@ class BchRouter {
|
||||
await _this.bchRESTController.getStatus(ctx, next)
|
||||
}
|
||||
|
||||
async postProvider (ctx, next) {
|
||||
await _this.bchRESTController.postProvider(ctx, next)
|
||||
}
|
||||
|
||||
async postBalance (ctx, next) {
|
||||
await _this.bchRESTController.balance(ctx, next)
|
||||
}
|
||||
|
||||
async postUtxos (ctx, next) {
|
||||
await _this.bchRESTController.utxos(ctx, next)
|
||||
}
|
||||
|
||||
async postBroadcast (ctx, next) {
|
||||
await _this.bchRESTController.broadcast(ctx, next)
|
||||
}
|
||||
|
||||
async postTransactions (ctx, next) {
|
||||
await _this.bchRESTController.transactions(ctx, next)
|
||||
}
|
||||
|
||||
async postTransaction (ctx, next) {
|
||||
await _this.bchRESTController.transaction(ctx, next)
|
||||
}
|
||||
|
||||
async postPubKey (ctx, next) {
|
||||
await _this.bchRESTController.pubKey(ctx, next)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BchRouter
|
||||
|
||||
@@ -12,6 +12,8 @@ const UserRouter = require('./users')
|
||||
const ContactRESTController = require('./contact')
|
||||
const LogsRESTController = require('./logs')
|
||||
const BchRESTController = require('./bch')
|
||||
const IpfsRESTController = require('./ipfs')
|
||||
const P2wdbRESTController = require('./p2wdb')
|
||||
|
||||
class RESTControllers {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -57,6 +59,14 @@ class RESTControllers {
|
||||
// Attach the REST API Controllers associated with the /bch route
|
||||
const bchRESTController = new BchRESTController(dependencies)
|
||||
bchRESTController.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 /p2wdb route
|
||||
const p2wdbRESTController = new P2wdbRESTController(dependencies)
|
||||
p2wdbRESTController.attach(app)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
REST API Controller library for the /ipfs route
|
||||
*/
|
||||
|
||||
const { wlogger } = require('../../../adapters/wlogger')
|
||||
|
||||
let _this
|
||||
|
||||
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
|
||||
|
||||
_this = 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)
|
||||
}
|
||||
}
|
||||
|
||||
// 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
|
||||
@@ -0,0 +1,68 @@
|
||||
/*
|
||||
REST API library for the /ipfs route.
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const Router = require('koa-router')
|
||||
|
||||
// Local libraries.
|
||||
const IPFSRESTControllerLib = require('./controller')
|
||||
const Validators = require('../middleware/validators')
|
||||
|
||||
// let _this
|
||||
|
||||
class BchRouter {
|
||||
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)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(this.router.routes())
|
||||
app.use(this.router.allowedMethods())
|
||||
}
|
||||
|
||||
// async getStatus (ctx, next) {
|
||||
// await _this.ipfsRESTController.getStatus(ctx, next)
|
||||
// }
|
||||
}
|
||||
|
||||
module.exports = BchRouter
|
||||
@@ -0,0 +1,138 @@
|
||||
/*
|
||||
REST API Controller library for the /p2wdb route
|
||||
*/
|
||||
|
||||
const { wlogger } = require('../../../adapters/wlogger')
|
||||
|
||||
// let _this
|
||||
|
||||
class P2wdbRESTControllerLib {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating /p2wdb REST Controller.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating /p2wdb REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
// this.UserModel = this.adapters.localdb.Users
|
||||
// this.userUseCases = this.useCases.user
|
||||
|
||||
// _this = this
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {get} /p2wdb Get status on P2WDB infrastructure
|
||||
* @apiPermission public
|
||||
* @apiName GetP2wdbStatus
|
||||
* @apiGroup REST BCH
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X GET localhost:5001/p2wdb
|
||||
*
|
||||
*/
|
||||
async getStatus (ctx) {
|
||||
try {
|
||||
const status = await this.adapters.p2wdb.getStatus()
|
||||
|
||||
ctx.body = { status }
|
||||
} catch (err) {
|
||||
wlogger.error('Error in bch/controller.js/getStatus(): ')
|
||||
// ctx.throw(422, err.message)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /p2wdb/provider Provider
|
||||
* @apiName Provider
|
||||
* @apiGroup REST P2WDB
|
||||
* @apiDescription Select a different P2WDB service on the IPFS network, for
|
||||
* interacting with the P2WDB.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "provider": "QmXtHADckCmT6jodpAgn3TcDQWjC29gQd2fKKHDTpo8DJT" }' localhost:5001/p2wdb/provider
|
||||
*/
|
||||
async postProvider (ctx) {
|
||||
try {
|
||||
const providerId = ctx.request.body.provider
|
||||
|
||||
await this.adapters.p2wdb.selectProvider(providerId)
|
||||
|
||||
const body = {
|
||||
success: true
|
||||
}
|
||||
|
||||
ctx.body = body
|
||||
} catch (err) {
|
||||
wlogger.error('Error in p2wdb/controller.js/postProvider(): ', err)
|
||||
// ctx.throw(422, err.message)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// curl -H "Content-Type: application/json" -X POST -d '{ "hash": "zdpuAzNVEHYegiazAmbnQtvQpEvJGsdXDbcH7vHFYx4NWDUqk" }' localhost:5001/p2wdb/entryFromHash
|
||||
async getEntryByHash (ctx) {
|
||||
try {
|
||||
const hash = ctx.request.body.hash
|
||||
|
||||
const data = await this.adapters.p2wdb.getEntryByHash(hash)
|
||||
|
||||
const body = {
|
||||
success: true,
|
||||
data
|
||||
}
|
||||
|
||||
ctx.body = body
|
||||
} catch (err) {
|
||||
wlogger.error('Error in p2wdb/controller.js/getEntryByHash(): ', err)
|
||||
// ctx.throw(422, err.message)
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
async writeEntry (ctx) {
|
||||
try {
|
||||
const txid = ctx.request.body.txid
|
||||
const signature = ctx.request.body.signature
|
||||
const message = ctx.request.body.message
|
||||
const data = ctx.request.body.data
|
||||
const writeObj = { txid, signature, message, data }
|
||||
|
||||
const hash = await this.adapters.p2wdb.writeEntry(writeObj)
|
||||
|
||||
ctx.body = {
|
||||
success: true,
|
||||
hash
|
||||
}
|
||||
} catch (err) {
|
||||
wlogger.error('Error in p2wdb/controller.js/writeEntry(): ', err)
|
||||
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 = P2wdbRESTControllerLib
|
||||
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
REST API library for /p2wdb route.
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const Router = require('koa-router')
|
||||
|
||||
// Local libraries.
|
||||
const P2wdbRESTControllerLib = require('./controller')
|
||||
const Validators = require('../middleware/validators')
|
||||
|
||||
let _this
|
||||
|
||||
class P2wdbRouter {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency Injection.
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of Adapters library required when instantiating P2WDB REST Controller.'
|
||||
)
|
||||
}
|
||||
this.useCases = localConfig.useCases
|
||||
if (!this.useCases) {
|
||||
throw new Error(
|
||||
'Instance of Use Cases library required when instantiating P2WDB REST Controller.'
|
||||
)
|
||||
}
|
||||
|
||||
const dependencies = {
|
||||
adapters: this.adapters,
|
||||
useCases: this.useCases
|
||||
}
|
||||
|
||||
// Encapsulate dependencies.
|
||||
this.p2wdbRESTController = new P2wdbRESTControllerLib(dependencies)
|
||||
this.validators = new Validators()
|
||||
|
||||
// Instantiate the router and set the base route.
|
||||
const baseUrl = '/p2wdb'
|
||||
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.getStatus)
|
||||
this.router.post('/provider', this.postProvider)
|
||||
this.router.post('/entryFromHash', this.entryFromHash)
|
||||
this.router.post('/write', this.write)
|
||||
// this.router.post('/broadcast', this.postBroadcast)
|
||||
// this.router.post('/transactions', this.postTransactions)
|
||||
// this.router.post('/transaction', this.postTransaction)
|
||||
// this.router.post('/pubkey', this.postPubKey)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(this.router.routes())
|
||||
app.use(this.router.allowedMethods())
|
||||
}
|
||||
|
||||
async getStatus (ctx, next) {
|
||||
await _this.p2wdbRESTController.getStatus(ctx, next)
|
||||
}
|
||||
|
||||
async postProvider (ctx, next) {
|
||||
await _this.p2wdbRESTController.postProvider(ctx, next)
|
||||
}
|
||||
|
||||
async entryFromHash (ctx, next) {
|
||||
await _this.p2wdbRESTController.getEntryByHash(ctx, next)
|
||||
}
|
||||
|
||||
async write (ctx, next) {
|
||||
await _this.p2wdbRESTController.writeEntry(ctx, next)
|
||||
}
|
||||
|
||||
// async postBroadcast (ctx, next) {
|
||||
// await _this.bchRESTController.broadcast(ctx, next)
|
||||
// }
|
||||
//
|
||||
// async postTransactions (ctx, next) {
|
||||
// await _this.bchRESTController.transactions(ctx, next)
|
||||
// }
|
||||
//
|
||||
// async postTransaction (ctx, next) {
|
||||
// await _this.bchRESTController.transaction(ctx, next)
|
||||
// }
|
||||
//
|
||||
// async postPubKey (ctx, next) {
|
||||
// await _this.bchRESTController.pubKey(ctx, next)
|
||||
// }
|
||||
}
|
||||
|
||||
module.exports = P2wdbRouter
|
||||
@@ -1,176 +0,0 @@
|
||||
/*
|
||||
This library contains business-logic for dealing with BCH wallet service
|
||||
providers. Most of these functions are called by the /bch REST API endpoints.
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const { v4: uid } = require('uuid')
|
||||
const jsonrpc = require('jsonrpc-lite')
|
||||
|
||||
// Local libraries
|
||||
const { wlogger } = require('../adapters/wlogger')
|
||||
|
||||
let _this
|
||||
|
||||
class BchUseCases {
|
||||
constructor (localConfig = {}) {
|
||||
// console.log('User localConfig: ', localConfig)
|
||||
this.adapters = localConfig.adapters
|
||||
if (!this.adapters) {
|
||||
throw new Error(
|
||||
'Instance of adapters must be passed in when instantiating BCH Use Cases library.'
|
||||
)
|
||||
}
|
||||
this.eventEmitter = localConfig.eventEmitter
|
||||
if (!this.eventEmitter) {
|
||||
throw new Error(
|
||||
'An instance of an EventEmitter must be passed when instantiating the BCH Use Cases library.'
|
||||
)
|
||||
}
|
||||
|
||||
// Connect the RPC handler when the event fires with new data.
|
||||
this.eventEmitter.on('rpcData', this.rpcHandler)
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.uid = uid
|
||||
this.jsonrpc = jsonrpc
|
||||
|
||||
// A queue for holding RPC data that has arrived.
|
||||
this.rpcDataQueue = []
|
||||
|
||||
_this = this // Global handle on instance of this Class.
|
||||
}
|
||||
|
||||
// This handler is triggered when RPC data comes in over IPFS.
|
||||
// Handle RPC input, and add the response to the RPC queue.
|
||||
// Once in the queue, it will get processed by waitForRPCResponse()
|
||||
rpcHandler (data) {
|
||||
try {
|
||||
// Convert string input into an object.
|
||||
// const jsonData = JSON.parse(data)
|
||||
|
||||
console.log(`JSON RPC response for ID ${data.payload.id} received.`)
|
||||
|
||||
_this.rpcDataQueue.push(data)
|
||||
} catch (err) {
|
||||
console.error('Error in rest-api.js/rpcHandler(): ', err)
|
||||
// Do not throw error. This is a top-level function.
|
||||
}
|
||||
}
|
||||
|
||||
async getStatus () {
|
||||
try {
|
||||
console.log(
|
||||
'this.adapters.ipfs.ipfsCoordAdapter.state: ',
|
||||
this.adapters.ipfs.ipfsCoordAdapter.state
|
||||
)
|
||||
|
||||
const status = {
|
||||
state: this.adapters.ipfs.ipfsCoordAdapter.state
|
||||
}
|
||||
|
||||
return status
|
||||
} catch (err) {
|
||||
// console.log('createUser() error: ', err)
|
||||
wlogger.error('Error in use-cases/bch.js/getStatus()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Get the BCH balance for an array of addresses.
|
||||
async getBalances (addrs) {
|
||||
try {
|
||||
// console.log('addrs: ', addrs)
|
||||
|
||||
// Throw an error if this IPFS node has not yet made a connection to a
|
||||
// wallet service provider.
|
||||
const selectedProvider = this.adapters.ipfs.ipfsCoordAdapter.state.selectedServiceProvider
|
||||
if (!selectedProvider) {
|
||||
throw new Error('No BCH Wallet Service provider available yet.')
|
||||
}
|
||||
|
||||
const rpcData = {
|
||||
endpoint: 'balance',
|
||||
addresses: addrs
|
||||
}
|
||||
|
||||
// Generate a UUID for the call.
|
||||
const rpcId = this.uid()
|
||||
|
||||
// Generate a JSON RPC command.
|
||||
const cmd = this.jsonrpc.request(rpcId, 'bch', rpcData)
|
||||
const cmdStr = JSON.stringify(cmd)
|
||||
// console.log('cmdStr: ', cmdStr)
|
||||
|
||||
// Send the RPC command to selected wallet service.
|
||||
const thisNode = this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.thisNode
|
||||
await this.adapters.ipfs.ipfsCoordAdapter.ipfsCoord.useCases.peer.sendPrivateMessage(
|
||||
selectedProvider,
|
||||
cmdStr,
|
||||
thisNode
|
||||
)
|
||||
|
||||
// Wait for data to come back from the wallet service.
|
||||
const data = await this.waitForRPCResponse(rpcId)
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
// console.log('createUser() error: ', err)
|
||||
wlogger.error('Error in use-cases/bch.js/getBalances()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a promise that resolves to data when the RPC response is recieved.
|
||||
async waitForRPCResponse (rpcId) {
|
||||
try {
|
||||
// Initialize variables for tracking the return data.
|
||||
let dataFound = false
|
||||
let cnt = 0
|
||||
|
||||
// Default return value, if the remote computer does not respond in time.
|
||||
let data = {
|
||||
success: false,
|
||||
message: 'request timed out',
|
||||
data: ''
|
||||
}
|
||||
|
||||
// Loop that waits for a response from the service provider.
|
||||
do {
|
||||
// console.log(`this.rpcDataQueue.length: ${this.rpcDataQueue.length}`)
|
||||
for (let i = 0; i < this.rpcDataQueue.length; i++) {
|
||||
const rawData = this.rpcDataQueue[i]
|
||||
// console.log(`rawData: ${JSON.stringify(rawData, null, 2)}`)
|
||||
|
||||
if (rawData.payload.id === rpcId) {
|
||||
dataFound = true
|
||||
// console.log('data was found in the queue')
|
||||
|
||||
data = rawData.payload.result.value
|
||||
|
||||
// Remove the data from the queue
|
||||
this.rpcDataQueue.splice(i, 1)
|
||||
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
// Wait between loops.
|
||||
// await this.sleep(1000)
|
||||
await this.adapters.ipfs.ipfsCoordAdapter.bchjs.Util.sleep(2000)
|
||||
|
||||
cnt++
|
||||
|
||||
// Exit if data was returned, or the window for a response expires.
|
||||
} while (!dataFound && cnt < 10)
|
||||
// console.log(`dataFound: ${dataFound}, cnt: ${cnt}`)
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
console.error('Error in waitForRPCResponse()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BchUseCases
|
||||
@@ -5,7 +5,6 @@
|
||||
*/
|
||||
|
||||
const UserUseCases = require('./user')
|
||||
const BchUseCases = require('./bch')
|
||||
|
||||
class UseCases {
|
||||
constructor (localConfig = {}) {
|
||||
@@ -24,7 +23,6 @@ class UseCases {
|
||||
|
||||
// console.log('use-cases/index.js localConfig: ', localConfig)
|
||||
this.user = new UserUseCases(localConfig)
|
||||
this.bch = new BchUseCases(localConfig)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# Manual End-to-End Tests
|
||||
Each test in this directory is used to debug live systems.
|
||||
Read the directions at the top of each test file.
|
||||
Run each test with `node <testname>.js`
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
/*
|
||||
To run this end-to-end test:
|
||||
|
||||
- Start the REST API server with `npm start`, wait for it to find a BCH wallet server.
|
||||
- Run this test app with `mocha --timeout=30000 bch-e2e.js`
|
||||
|
||||
This test will query each REST API endpoint and ensure they are working correctly.
|
||||
*/
|
||||
|
||||
const SERVER = 'http://localhost:5001'
|
||||
|
||||
const axios = require('axios')
|
||||
const assert = require('chai').assert
|
||||
|
||||
describe('#BCH E2E Tests', () => {
|
||||
console.log(
|
||||
'Ensure the REST API app has been started and has found a BCH wallet service provider, before running this test.'
|
||||
)
|
||||
|
||||
describe('#status', () => {
|
||||
it('should report at least one service provider', async () => {
|
||||
const url = `${SERVER}/bch`
|
||||
const result = await axios.get(url)
|
||||
// console.log(`data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert.isAbove(result.data.status.state.serviceProviders.length, 0)
|
||||
assert.isOk(result.data.status.state.selectedServiceProvider)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#balance', () => {
|
||||
it('should get the balance for an address', async () => {
|
||||
const body = {
|
||||
addresses: 'bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj'
|
||||
}
|
||||
|
||||
const url = `${SERVER}/bch/balance`
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert.equal(result.data.success, true)
|
||||
assert.equal(result.data.status, 200)
|
||||
assert.property(result.data, 'balance')
|
||||
assert.property(result.data.balance, 'confirmed')
|
||||
assert.property(result.data.balance, 'unconfirmed')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#utxos', () => {
|
||||
it('should get UTXOs for an address', async () => {
|
||||
const body = {
|
||||
address: 'bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj'
|
||||
}
|
||||
|
||||
const url = `${SERVER}/bch/utxos`
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert.equal(result.data.address, body.address)
|
||||
assert.property(result.data, 'bchUtxos')
|
||||
assert.property(result.data, 'slpUtxos')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#broadcast', () => {
|
||||
it('should attempt to broadcast a transaction', async () => {
|
||||
const body = {
|
||||
hex: '01000000013ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a000000006a4730440220540986d1c58d6e76f8f05501c520c38ce55393d0ed7ed3c3a82c69af04221232022058ea43ed6c05fec0eccce749a63332ed4525460105346f11108b9c26df93cd72012103083dfc5a0254613941ddc91af39ff90cd711cdcde03a87b144b883b524660c39ffffffff01807c814a000000001976a914d7e7c4e0b70eaa67ceff9d2823d1bbb9f6df9a5188ac00000000'
|
||||
}
|
||||
|
||||
const url = `${SERVER}/bch/broadcast`
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert.equal(result.data.status, 422)
|
||||
assert.equal(result.data.success, false)
|
||||
assert.equal(result.data.message, 'TX decode failed')
|
||||
assert.equal(result.data.endpoint, 'broadcast')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#transactions', () => {
|
||||
it('should get transaction history for an address', async () => {
|
||||
const body = {
|
||||
address: 'bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj'
|
||||
}
|
||||
|
||||
const url = `${SERVER}/bch/transactions`
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert.equal(result.data.status, 200)
|
||||
assert.equal(result.data.success, true)
|
||||
assert.property(result.data, 'transactions')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#transaction', () => {
|
||||
it('should get transaction details for a txid', async () => {
|
||||
const body = {
|
||||
txid: '01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b'
|
||||
}
|
||||
|
||||
const url = `${SERVER}/bch/transaction`
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert.property(result.data, 'txid')
|
||||
assert.property(result.data, 'vin')
|
||||
})
|
||||
})
|
||||
|
||||
describe('#pubkey', () => {
|
||||
it('should return "not found" if pubkey is not on chain', async () => {
|
||||
const body = {
|
||||
address: 'bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj'
|
||||
}
|
||||
|
||||
const url = `${SERVER}/bch/pubkey`
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert.equal(result.data.success, true)
|
||||
assert.equal(result.data.status, 200)
|
||||
assert.equal(result.data.endpoint, 'pubkey')
|
||||
assert.equal(result.data.pubkey.success, false)
|
||||
assert.equal(result.data.pubkey.publicKey, 'not found')
|
||||
})
|
||||
|
||||
it('should get a public key for an address', async () => {
|
||||
const body = {
|
||||
address: 'bitcoincash:qznxh6yegf7nfrsd0zeksed4jcqzvg7tzqlcqw3v97'
|
||||
}
|
||||
|
||||
const url = `${SERVER}/bch/pubkey`
|
||||
const result = await axios.post(url, body)
|
||||
// console.log(`result.data: ${JSON.stringify(result.data, null, 2)}`)
|
||||
|
||||
assert.equal(result.data.success, true)
|
||||
assert.equal(result.data.status, 200)
|
||||
assert.equal(result.data.endpoint, 'pubkey')
|
||||
assert.equal(result.data.pubkey.success, true)
|
||||
assert.equal(
|
||||
result.data.pubkey.publicKey,
|
||||
'03ebd6d6aae05da1c5905445e3886b30f6a31b26a88de5980de236bd22380fa942'
|
||||
)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,287 @@
|
||||
/*
|
||||
Unit tests for the src/lib/users.js business logic library.
|
||||
|
||||
TODO: verify that an admin can change the type of a user
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
const EventEmitter = require('events')
|
||||
const cloneDeep = require('lodash.clonedeep')
|
||||
|
||||
// Local support libraries
|
||||
// const testUtils = require('../../utils/test-utils')
|
||||
|
||||
// Unit under test (uut)
|
||||
const BchAdapter = require('../../../src/adapters/bch')
|
||||
const adapters = require('../mocks/adapters')
|
||||
const eventEmitter = new EventEmitter()
|
||||
const mockDataLib = require('../mocks/use-cases/rest-api-mocks')
|
||||
|
||||
describe('#bch-use-case', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
let mockData
|
||||
|
||||
before(async () => {
|
||||
// Delete all previous users in the database.
|
||||
// await testUtils.deleteAllUsers()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
mockData = cloneDeep(mockDataLib)
|
||||
|
||||
const ipfs = adapters.ipfs
|
||||
uut = new BchAdapter({ ipfs, eventEmitter })
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should throw an error if ipfs is not passed in', () => {
|
||||
try {
|
||||
uut = new BchAdapter()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'An instance of IPFS must be passed when instantiating the BCH Adapter library.'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if adapters are not passed in', () => {
|
||||
try {
|
||||
const ipfs = {}
|
||||
uut = new BchAdapter({ ipfs })
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'An instance of an EventEmitter must be passed when instantiating the adapters.'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#waitForRPCResponse', () => {
|
||||
it('should resolve when data is received', async () => {
|
||||
// Mock dependencies
|
||||
uut.ipfs.ipfsCoordAdapter.bchjs = {
|
||||
Util: {
|
||||
sleep: () => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Mock data.
|
||||
const rpcId = '123'
|
||||
uut.rpcDataQueue.push(mockData.rpcData)
|
||||
|
||||
const result = await uut.waitForRPCResponse(rpcId)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'success')
|
||||
assert.equal(result.success, true)
|
||||
assert.property(result, 'balances')
|
||||
assert.isArray(result.balances)
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
uut.ipfs.ipfsCoordAdapter.bchjs = {
|
||||
Util: {
|
||||
sleep: () => {
|
||||
throw new Error('test error')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mock data.
|
||||
const rpcId = '123'
|
||||
uut.rpcDataQueue.push(mockData.rpcData)
|
||||
|
||||
await uut.waitForRPCResponse(rpcId)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('error: ', err)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#rpcHandler', () => {
|
||||
it('should add RPC data to queue', () => {
|
||||
const data = {
|
||||
payload: {
|
||||
id: '123'
|
||||
}
|
||||
}
|
||||
|
||||
uut.rpcHandler(data)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getStatus', () => {
|
||||
it('should get the status from ipfs-coord', async () => {
|
||||
const result = await uut.getStatus()
|
||||
console.log('result: ', result)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getBalances', () => {
|
||||
it('should get the balance of an address', async () => {
|
||||
// Force connection to a wallet service
|
||||
uut.ipfs.ipfsCoordAdapter.state = {
|
||||
selectedServiceProvider: 'abc123'
|
||||
}
|
||||
|
||||
// Mock depenencies
|
||||
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
|
||||
|
||||
const addrs = ['addr1']
|
||||
|
||||
const result = await uut.getBalances(addrs)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.key, 'value')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
await uut.getBalances()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#broadcast', () => {
|
||||
it('should broadcast a transaction', async () => {
|
||||
// Force connection to a wallet service
|
||||
uut.ipfs.ipfsCoordAdapter.state = {
|
||||
selectedServiceProvider: 'abc123'
|
||||
}
|
||||
|
||||
// Mock depenencies
|
||||
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
|
||||
|
||||
const hex = 'abc123'
|
||||
|
||||
const result = await uut.broadcast(hex)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.key, 'value')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
await uut.broadcast()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTransactions', () => {
|
||||
it('should get transactions history for an address', async () => {
|
||||
// Force connection to a wallet service
|
||||
uut.ipfs.ipfsCoordAdapter.state = {
|
||||
selectedServiceProvider: 'abc123'
|
||||
}
|
||||
|
||||
// Mock depenencies
|
||||
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
|
||||
|
||||
const addr = 'abc123'
|
||||
|
||||
const result = await uut.getTransactions(addr)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.key, 'value')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
await uut.getTransactions()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTransaction', () => {
|
||||
it('should get transaction details for a txid', async () => {
|
||||
// Force connection to a wallet service
|
||||
uut.ipfs.ipfsCoordAdapter.state = {
|
||||
selectedServiceProvider: 'abc123'
|
||||
}
|
||||
|
||||
// Mock depenencies
|
||||
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
|
||||
|
||||
const txid = 'abc123'
|
||||
|
||||
const result = await uut.getTransaction(txid)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.key, 'value')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
await uut.getTransaction()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getPubKey', () => {
|
||||
it('should get transaction details for a txid', async () => {
|
||||
// Force connection to a wallet service
|
||||
uut.ipfs.ipfsCoordAdapter.state = {
|
||||
selectedServiceProvider: 'abc123'
|
||||
}
|
||||
|
||||
// Mock depenencies
|
||||
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
|
||||
|
||||
const addr = 'abc123'
|
||||
|
||||
const result = await uut.getPubKey(addr)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.key, 'value')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
await uut.getPubKey()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -29,7 +29,8 @@ describe('#IPFS', () => {
|
||||
afterEach(() => {
|
||||
sandbox.restore()
|
||||
|
||||
clearInterval(uut.pollServiceInterval)
|
||||
clearInterval(uut.pollBchServiceInterval)
|
||||
clearInterval(uut.pollP2wdbServiceInterval)
|
||||
})
|
||||
|
||||
describe('#constructor', () => {
|
||||
@@ -125,7 +126,7 @@ describe('#IPFS', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#pollForServices', () => {
|
||||
describe('#pollForBchServices', () => {
|
||||
it('should find and select the wallet service', () => {
|
||||
uut.ipfsCoord = {
|
||||
thisNode: {
|
||||
@@ -134,7 +135,7 @@ describe('#IPFS', () => {
|
||||
}
|
||||
}
|
||||
|
||||
uut.pollForServices()
|
||||
uut.pollForBchServices()
|
||||
|
||||
// It should fine the service in the mocked data.
|
||||
assert.equal(
|
||||
@@ -144,7 +145,7 @@ describe('#IPFS', () => {
|
||||
})
|
||||
|
||||
it('should catch and report errors', () => {
|
||||
uut.pollForServices()
|
||||
uut.pollForBchServices()
|
||||
|
||||
assert.isOk(true, 'Not throwing an error is a success.')
|
||||
})
|
||||
|
||||
@@ -60,4 +60,25 @@ describe('#IPFS-adapter', () => {
|
||||
assert.equal(result, true)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#rmBlocksDir', () => {
|
||||
it('should delete the /blocks directory', () => {
|
||||
const result = uut.rmBlocksDir()
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
|
||||
it('should catch and throw an error', () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox.stub(uut.fs, 'rmdirSync').throws(new Error('test error'))
|
||||
|
||||
uut.rmBlocksDir()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.equal(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -79,7 +79,7 @@ describe('#BCH-REST-Controller', () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.useCases.bch, 'getStatus')
|
||||
.stub(uut.adapters.bch, 'getStatus')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
await uut.getStatus(ctx)
|
||||
@@ -93,7 +93,7 @@ describe('#BCH-REST-Controller', () => {
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
sandbox.stub(uut.useCases.bch, 'getStatus').resolves('bch')
|
||||
sandbox.stub(uut.adapters.bch, 'getStatus').resolves('bch')
|
||||
|
||||
await uut.getStatus(ctx)
|
||||
|
||||
@@ -106,11 +106,11 @@ describe('#BCH-REST-Controller', () => {
|
||||
})
|
||||
|
||||
describe('#balance', () => {
|
||||
it('should return 422 status on arbitrary biz logic error', async () => {
|
||||
it('should return 422 status on arbitrary error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.useCases.bch, 'getBalances')
|
||||
.stub(uut.adapters.bch, 'getBalances')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
ctx.request.body = {
|
||||
@@ -128,13 +128,195 @@ describe('#BCH-REST-Controller', () => {
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
sandbox.stub(uut.useCases.bch, 'getBalances').resolves({ status: 200 })
|
||||
sandbox.stub(uut.adapters.bch, 'getBalances').resolves({ status: 200 })
|
||||
|
||||
ctx.request.body = {
|
||||
addresses: 'blah'
|
||||
}
|
||||
|
||||
await uut.getStatus(ctx)
|
||||
await uut.balance(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#utxos', () => {
|
||||
it('should return 422 status on arbitrary error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.adapters.bch, 'getUtxos')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
ctx.request.body = {
|
||||
address: 'blah'
|
||||
}
|
||||
|
||||
await uut.utxos(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
console.log('err: ', err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
sandbox.stub(uut.adapters.bch, 'getUtxos').resolves({ status: 200 })
|
||||
|
||||
ctx.request.body = {
|
||||
address: 'blah'
|
||||
}
|
||||
|
||||
await uut.utxos(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#broadcast', () => {
|
||||
it('should return 422 status on arbitrary error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.adapters.bch, 'broadcast')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
ctx.request.body = {
|
||||
hex: 'blah'
|
||||
}
|
||||
|
||||
await uut.broadcast(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
console.log('err: ', err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
sandbox.stub(uut.adapters.bch, 'broadcast').resolves({ status: 200 })
|
||||
|
||||
ctx.request.body = {
|
||||
hex: 'blah'
|
||||
}
|
||||
|
||||
await uut.broadcast(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#transactions', () => {
|
||||
it('should return 422 status on arbitrary error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.adapters.bch, 'getTransactions')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
ctx.request.body = {
|
||||
address: 'blah'
|
||||
}
|
||||
|
||||
await uut.transactions(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
console.log('err: ', err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
sandbox.stub(uut.adapters.bch, 'getUtxos').resolves({ status: 200 })
|
||||
|
||||
ctx.request.body = {
|
||||
address: 'blah'
|
||||
}
|
||||
|
||||
await uut.transactions(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#transaction', () => {
|
||||
it('should return 422 status on arbitrary error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.adapters.bch, 'getTransaction')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
ctx.request.body = {
|
||||
txid: 'blah'
|
||||
}
|
||||
|
||||
await uut.transaction(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
console.log('err: ', err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
sandbox
|
||||
.stub(uut.adapters.bch, 'getTransactions')
|
||||
.resolves({ status: 200 })
|
||||
|
||||
ctx.request.body = {
|
||||
txid: 'blah'
|
||||
}
|
||||
|
||||
await uut.transaction(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#pubKey', () => {
|
||||
it('should return 422 status on arbitrary error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.adapters.bch, 'getPubKey')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
ctx.request.body = {
|
||||
addr: 'blah'
|
||||
}
|
||||
|
||||
await uut.pubKey(ctx)
|
||||
|
||||
assert.fail('Unexpected result')
|
||||
} catch (err) {
|
||||
console.log('err: ', err)
|
||||
assert.equal(err.status, 422)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
|
||||
it('should return 200 status on success', async () => {
|
||||
sandbox.stub(uut.adapters.bch, 'getPubKey').resolves({ status: 200 })
|
||||
|
||||
ctx.request.body = {
|
||||
addr: 'blah'
|
||||
}
|
||||
|
||||
await uut.pubKey(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
|
||||
@@ -12,9 +12,15 @@ const ipfs = {
|
||||
peer: {
|
||||
sendPrivateMessage: () => {}
|
||||
}
|
||||
},
|
||||
thisNode: {
|
||||
peerData: []
|
||||
}
|
||||
},
|
||||
peerInputHandler: () => {}
|
||||
peerInputHandler: () => {},
|
||||
state: {
|
||||
serviceProviders: []
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,4 +60,43 @@ const localdb = {
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { ipfs, localdb }
|
||||
class BchUseCaseMock {
|
||||
rpcHandler () {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getStatus () {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getBalances () {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getUtxos () {
|
||||
return {}
|
||||
}
|
||||
|
||||
async broadcast () {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getTransaction () {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getTransactions () {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getPubKey () {
|
||||
return {}
|
||||
}
|
||||
|
||||
async waitForRPCResponse () {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
const bch = new BchUseCaseMock()
|
||||
|
||||
module.exports = { ipfs, localdb, bch }
|
||||
|
||||
@@ -46,7 +46,7 @@ const peerData = [
|
||||
'@context': 'https://schema.org/',
|
||||
'@type': 'WebAPI',
|
||||
name: 'trout-bch-wallet-service-dev',
|
||||
version: '1.11.1',
|
||||
version: '1.11.12',
|
||||
protocol: 'bch-wallet',
|
||||
description:
|
||||
'IPFS service providing BCH blockchain access needed by a wallet.',
|
||||
|
||||
@@ -1,165 +0,0 @@
|
||||
/*
|
||||
Unit tests for the src/lib/users.js business logic library.
|
||||
|
||||
TODO: verify that an admin can change the type of a user
|
||||
*/
|
||||
|
||||
// Public npm libraries
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
const EventEmitter = require('events')
|
||||
const cloneDeep = require('lodash.clonedeep')
|
||||
|
||||
// Local support libraries
|
||||
// const testUtils = require('../../utils/test-utils')
|
||||
|
||||
// Unit under test (uut)
|
||||
const BchUseCases = require('../../../src/use-cases/bch')
|
||||
const adapters = require('../mocks/adapters')
|
||||
const eventEmitter = new EventEmitter()
|
||||
const mockDataLib = require('../mocks/use-cases/rest-api-mocks')
|
||||
|
||||
describe('#bch-use-case', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
let mockData
|
||||
|
||||
before(async () => {
|
||||
// Delete all previous users in the database.
|
||||
// await testUtils.deleteAllUsers()
|
||||
})
|
||||
|
||||
beforeEach(() => {
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
mockData = cloneDeep(mockDataLib)
|
||||
|
||||
uut = new BchUseCases({ adapters, eventEmitter })
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should throw an error if adapters are not passed in', () => {
|
||||
try {
|
||||
uut = new BchUseCases()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'Instance of adapters must be passed in when instantiating BCH Use Cases library.'
|
||||
)
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if adapters are not passed in', () => {
|
||||
try {
|
||||
uut = new BchUseCases({ adapters })
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'An instance of an EventEmitter must be passed when instantiating the BCH Use Cases library.'
|
||||
)
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#waitForRPCResponse', () => {
|
||||
it('should should resolve when data is received', async () => {
|
||||
// Mock dependencies
|
||||
uut.adapters.ipfs.ipfsCoordAdapter.bchjs = {
|
||||
Util: {
|
||||
sleep: () => {}
|
||||
}
|
||||
}
|
||||
|
||||
// Mock data.
|
||||
const rpcId = '123'
|
||||
uut.rpcDataQueue.push(mockData.rpcData)
|
||||
|
||||
const result = await uut.waitForRPCResponse(rpcId)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.property(result, 'success')
|
||||
assert.equal(result.success, true)
|
||||
assert.property(result, 'balances')
|
||||
assert.isArray(result.balances)
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
uut.adapters.ipfs.ipfsCoordAdapter.bchjs = {
|
||||
Util: {
|
||||
sleep: () => {
|
||||
throw new Error('test error')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mock data.
|
||||
const rpcId = '123'
|
||||
uut.rpcDataQueue.push(mockData.rpcData)
|
||||
|
||||
await uut.waitForRPCResponse(rpcId)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log('error: ', err)
|
||||
assert.include(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#rpcHandler', () => {
|
||||
it('should add RPC data to queue', () => {
|
||||
const data = {
|
||||
payload: {
|
||||
id: '123'
|
||||
}
|
||||
}
|
||||
|
||||
uut.rpcHandler(data)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getStatus', () => {
|
||||
it('should get the status from ipfs-coord', async () => {
|
||||
const result = await uut.getStatus()
|
||||
console.log('result: ', result)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getBalances', () => {
|
||||
it('should get the balance of an address', async () => {
|
||||
// Force connection to a wallet service
|
||||
uut.adapters.ipfs.ipfsCoordAdapter.state = {
|
||||
selectedServiceProvider: 'abc123'
|
||||
}
|
||||
|
||||
// Mock depenencies
|
||||
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
|
||||
|
||||
const addrs = ['addr1']
|
||||
|
||||
const result = await uut.getBalances(addrs)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.key, 'value')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
await uut.getBalances()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user