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 | |
|---|---|---|---|
|
|
5b4d00b949 | ||
|
|
a666dbcafd | ||
|
|
3d6d544c21 | ||
|
|
2ca54865bc | ||
|
|
df778d6d8e | ||
|
|
3c64f44dfd | ||
|
|
45ea47d4d0 | ||
|
|
150e3d6799 | ||
|
|
b3011bcb69 | ||
|
|
ccbe9a6707 | ||
|
|
23ebdaa7f3 | ||
|
|
fb182ced58 | ||
|
|
bec7c11e46 | ||
|
|
fb10e10226 | ||
|
|
5b37e47747 | ||
|
|
a3cea782ba | ||
|
|
14ffdae194 | ||
|
|
5792871e0a | ||
|
|
c2ec08f03d | ||
|
|
e4489cfcac | ||
|
|
e8c08beeb3 | ||
|
|
4a64423885 | ||
|
|
7e3ce1564d | ||
|
|
8154dd96f9 | ||
|
|
e37223cc88 | ||
|
|
5002ef2b90 | ||
|
|
5d95109ad4 | ||
|
|
13b3150f62 | ||
|
|
b3a4a7c1ab |
@@ -38,13 +38,11 @@ npm install
|
||||
The [docker](./production/docker) directory contains a Dockerfile for building a production deployment.
|
||||
|
||||
```
|
||||
docker-compose build --no-cache
|
||||
docker-compose pull
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
- Bring the containers up once, then take them back down with `docker-compose down`.
|
||||
- Copy the [swarm.key](./swarm.key) file to `ipfs-service-provider/production/data/go-ipfs/data/swarm.key`.
|
||||
- Bring the containers back up with `docker-compose up -d`.
|
||||
You can bring the containers back up with `docker-compose up -d`.
|
||||
|
||||
### Operation Notes
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -94,7 +94,7 @@ module.exports = {
|
||||
: '',
|
||||
|
||||
// Settings for production, using external go-ipfs node.
|
||||
isProduction: process.env.SVC_ENV === 'production' ? true : false,
|
||||
isProduction: process.env.CONSUMER_ENV === 'production' ? true : false,
|
||||
ipfsHost: process.env.IPFS_HOST ? process.env.IPFS_HOST : 'localhost',
|
||||
ipfsApiPort: process.env.IPFS_API_PORT
|
||||
? parseInt(process.env.IPFS_API_PORT)
|
||||
|
||||
Generated
+2713
-3978
File diff suppressed because it is too large
Load Diff
+1
-1
@@ -23,7 +23,7 @@
|
||||
},
|
||||
"repository": "Permissionless-Software-Foundation/ipfs-bch-wallet-consumer",
|
||||
"dependencies": {
|
||||
"@psf/bch-js": "5.3.2",
|
||||
"@psf/bch-js": "6.4.1",
|
||||
"axios": "0.25.0",
|
||||
"bcryptjs": "2.4.3",
|
||||
"glob": "7.1.6",
|
||||
|
||||
@@ -12,7 +12,7 @@ RUN apt-get update
|
||||
RUN apt-get install -y sudo git curl nano gnupg wget
|
||||
|
||||
#Install Node and NPM
|
||||
RUN curl -sL https://deb.nodesource.com/setup_14.x -o nodesource_setup.sh
|
||||
RUN curl -sL https://deb.nodesource.com/setup_16.x -o nodesource_setup.sh
|
||||
RUN bash nodesource_setup.sh
|
||||
RUN apt-get install -y nodejs build-essential
|
||||
|
||||
@@ -46,11 +46,11 @@ RUN npm set registry http://94.130.170.209:4873/
|
||||
|
||||
# Clone the rest.bitcoin.com repository
|
||||
WORKDIR /home/safeuser
|
||||
RUN git clone https://github.com/Permissionless-Software-Foundation/ipfs-service-provider
|
||||
RUN git clone https://github.com/Permissionless-Software-Foundation/ipfs-bch-wallet-consumer
|
||||
|
||||
# Switch to the desired branch. `master` is usually stable,
|
||||
# and `stage` has the most up-to-date changes.
|
||||
WORKDIR /home/safeuser/ipfs-service-provider
|
||||
WORKDIR /home/safeuser/ipfs-bch-wallet-consumer
|
||||
|
||||
# For development: switch to unstable branch
|
||||
RUN git checkout ct-unstable
|
||||
@@ -69,7 +69,7 @@ VOLUME /home/safeuser/keys
|
||||
EXPOSE 5010
|
||||
|
||||
# Start the application.
|
||||
COPY start-production.sh start-production.sh
|
||||
#COPY start-production.sh start-production.sh
|
||||
CMD ["./start-production.sh"]
|
||||
|
||||
#CMD ["npm", "start"]
|
||||
|
||||
@@ -30,7 +30,7 @@ services:
|
||||
- 172.17.0.1:8080:8080
|
||||
volumes:
|
||||
- ../data/go-ipfs/data:/data/ipfs
|
||||
- ../data/go-ipfs/export:/export
|
||||
- ../../swarm.key:/data/ipfs/swarm.key
|
||||
# https://docs.docker.com/compose/compose-file/compose-file-v3/#command
|
||||
# https://github.com/ipfs/go-ipfs/blob/91c52657166bcf86f2476926e4fe56694dc26562/Dockerfile#L115
|
||||
command:
|
||||
@@ -44,6 +44,7 @@ services:
|
||||
|
||||
bch-consumer:
|
||||
build: .
|
||||
#image: christroutner/ipfs-bch-wallet-consumer:v1.8.0
|
||||
container_name: bch-consumer
|
||||
logging:
|
||||
driver: 'json-file'
|
||||
@@ -57,5 +58,6 @@ services:
|
||||
ports:
|
||||
- '5010:5010' # <host port>:<container port>
|
||||
volumes:
|
||||
- ../data/ipfsdata:/home/safeuser/ipfs-service-provider/.ipfsdata
|
||||
- ../data/ipfsdata:/home/safeuser/ipfs-bch-wallet-consumer/.ipfsdata
|
||||
- ./start-production.sh:/home/safeuser/ipfs-bch-wallet-consumer/start-production.sh
|
||||
restart: always
|
||||
|
||||
@@ -29,7 +29,7 @@ export DBURL=mongodb://172.17.0.1:5555/ipfs-bch-consumer-prod
|
||||
export PORT=5010
|
||||
|
||||
# Production settings using external go-ipfs node.
|
||||
export SVC_ENV=production
|
||||
export CONSUMER_ENV=production
|
||||
export IPFS_HOST=172.17.0.1
|
||||
export IPFS_API_PORT=5001
|
||||
export IPFS_TCP_PORT=4001
|
||||
|
||||
@@ -13,7 +13,7 @@ export COORD_NAME=trout-dev-bch-wallet-consumer
|
||||
export MNEMONIC="stomach mean catch march census author cousin size south romance pumpkin trap"
|
||||
|
||||
# 0 = less verbose. 3 = most verbose
|
||||
export DEBUG_LEVEL=2
|
||||
export DEBUG_LEVEL=1
|
||||
|
||||
# Production settings that use external IPFS node.
|
||||
# https://github.com/christroutner/docker-ipfs
|
||||
|
||||
@@ -21,15 +21,15 @@ class BchAdapter {
|
||||
'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.'
|
||||
)
|
||||
}
|
||||
// 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)
|
||||
// this.eventEmitter.on('rpcData', this.rpcHandler)
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.uid = uid
|
||||
@@ -387,6 +387,90 @@ class BchAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
async utxoIsValid (utxo) {
|
||||
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: 'utxoIsValid',
|
||||
utxo
|
||||
}
|
||||
|
||||
// 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('utxoIsValid() error: ', err)
|
||||
wlogger.error('Error in adapters/bch.js/utxoIsValid()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async getTokenData (tokenId) {
|
||||
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: 'getTokenData',
|
||||
tokenId
|
||||
}
|
||||
|
||||
// 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('getTokenData() error: ', err)
|
||||
wlogger.error('Error in adapters/bch.js/getTokenData()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a promise that resolves to data when the RPC response is recieved.
|
||||
async waitForRPCResponse (rpcId) {
|
||||
try {
|
||||
|
||||
@@ -24,12 +24,12 @@ const config = require('../../config')
|
||||
class Adapters {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency injection.
|
||||
this.eventEmitter = localConfig.eventEmitter
|
||||
if (!this.eventEmitter) {
|
||||
throw new Error(
|
||||
'An instance of an EventEmitter must be passed when instantiating the adapters.'
|
||||
)
|
||||
}
|
||||
// this.eventEmitter = localConfig.eventEmitter
|
||||
// if (!this.eventEmitter) {
|
||||
// throw new Error(
|
||||
// 'An instance of an EventEmitter must be passed when instantiating the adapters.'
|
||||
// )
|
||||
// }
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.ipfs = new IPFSAdapter(localConfig)
|
||||
|
||||
@@ -8,12 +8,12 @@ const IpfsCoordAdapter = require('./ipfs-coord')
|
||||
class IPFS {
|
||||
constructor (localConfig = {}) {
|
||||
// Dependency injection.
|
||||
this.eventEmitter = localConfig.eventEmitter
|
||||
if (!this.eventEmitter) {
|
||||
throw new Error(
|
||||
'An instance of an EventEmitter must be passed when instantiating the ipfs-coord adapter.'
|
||||
)
|
||||
}
|
||||
// this.eventEmitter = localConfig.eventEmitter
|
||||
// if (!this.eventEmitter) {
|
||||
// throw new Error(
|
||||
// 'An instance of an EventEmitter must be passed when instantiating the ipfs-coord adapter.'
|
||||
// )
|
||||
// }
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.ipfsAdapter = new IpfsAdapter()
|
||||
@@ -39,8 +39,8 @@ class IPFS {
|
||||
|
||||
// Start ipfs-coord
|
||||
this.ipfsCoordAdapter = new this.IpfsCoordAdapter({
|
||||
ipfs: this.ipfs,
|
||||
eventEmitter: this.eventEmitter
|
||||
ipfs: this.ipfs
|
||||
// eventEmitter: this.eventEmitter
|
||||
})
|
||||
await this.ipfsCoordAdapter.start()
|
||||
console.log('ipfs-coord is ready.')
|
||||
|
||||
@@ -31,12 +31,12 @@ class IpfsCoordAdapter {
|
||||
'Instance of IPFS must be passed when instantiating ipfs-coord.'
|
||||
)
|
||||
}
|
||||
this.eventEmitter = localConfig.eventEmitter
|
||||
if (!this.eventEmitter) {
|
||||
throw new Error(
|
||||
'An instance of an EventEmitter must be passed when instantiating the ipfs-coord adapter.'
|
||||
)
|
||||
}
|
||||
// this.eventEmitter = localConfig.eventEmitter
|
||||
// if (!this.eventEmitter) {
|
||||
// throw new Error(
|
||||
// 'An instance of an EventEmitter must be passed when instantiating the ipfs-coord adapter.'
|
||||
// )
|
||||
// }
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.IpfsCoord = IpfsCoord
|
||||
@@ -291,16 +291,16 @@ class IpfsCoordAdapter {
|
||||
|
||||
// This method handles input coming in from other IPFS peers.
|
||||
// It passes the data on to the REST API library by emitting an event.
|
||||
peerInputHandler (data) {
|
||||
try {
|
||||
// console.log('peerInputHandler triggered with this data: ', data)
|
||||
|
||||
this.eventEmitter.emit('rpcData', data)
|
||||
} catch (err) {
|
||||
console.error('Error in ipfs-coord.js/peerInputHandler(): ', err)
|
||||
// Do not throw error. This is a top-level function.
|
||||
}
|
||||
}
|
||||
// peerInputHandler (data) {
|
||||
// try {
|
||||
// // console.log('peerInputHandler triggered with this data: ', data)
|
||||
//
|
||||
// this.eventEmitter.emit('rpcData', data)
|
||||
// } catch (err) {
|
||||
// console.error('Error in ipfs-coord.js/peerInputHandler(): ', err)
|
||||
// // Do not throw error. This is a top-level function.
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
||||
module.exports = IpfsCoordAdapter
|
||||
|
||||
+18
-18
@@ -102,9 +102,9 @@ class IpfsAdapter {
|
||||
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()
|
||||
}
|
||||
// if (err.message.includes('No space left on device')) {
|
||||
// this.rmBlocksDir()
|
||||
// }
|
||||
|
||||
throw err
|
||||
}
|
||||
@@ -120,21 +120,21 @@ class IpfsAdapter {
|
||||
// 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
|
||||
}
|
||||
}
|
||||
// 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
|
||||
|
||||
@@ -21,15 +21,15 @@ class P2wdbAdapter {
|
||||
'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.'
|
||||
)
|
||||
}
|
||||
// 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)
|
||||
// this.eventEmitter.on('rpcData', this.rpcHandler)
|
||||
|
||||
// Encapsulate dependencies
|
||||
this.uid = uid
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
*/
|
||||
|
||||
// Public npm libraries.
|
||||
const EventEmitter = require('events')
|
||||
// const EventEmitter = require('events')
|
||||
|
||||
// Local libraries
|
||||
const config = require('../../config')
|
||||
@@ -20,8 +20,8 @@ class Controllers {
|
||||
this.config = config
|
||||
|
||||
// Initialize EventEmitters used to pass event-driven data around the app.
|
||||
this.eventEmitter = new EventEmitter()
|
||||
localConfig.eventEmitter = this.eventEmitter
|
||||
// this.eventEmitter = new EventEmitter()
|
||||
// localConfig.eventEmitter = this.eventEmitter
|
||||
|
||||
// Initialize Clean Architecture classes. Inject dependencies.
|
||||
this.adapters = new Adapters(localConfig)
|
||||
|
||||
@@ -90,13 +90,14 @@ class JSONRPC {
|
||||
}
|
||||
|
||||
// Process response from REST API.
|
||||
_this.adapters.ipfs.ipfsCoordAdapter.peerInputHandler(parsedData)
|
||||
// _this.adapters.ipfs.ipfsCoordAdapter.peerInputHandler(parsedData)
|
||||
|
||||
// Log the incoming JSON RPC command.
|
||||
wlogger.info(
|
||||
`JSON RPC received from ${from}, ID: ${parsedData.payload.id}, type: ${parsedData.type}`
|
||||
)
|
||||
}
|
||||
// console.log(`parsedData: ${JSON.stringify(parsedData, null, 2)}`)
|
||||
|
||||
// Added the property "from" to the parsedData object;
|
||||
// necessary for calculating rate limits (based on the IPFS ID).
|
||||
@@ -105,6 +106,16 @@ class JSONRPC {
|
||||
// Default return string
|
||||
let retObj = _this.defaultResponse()
|
||||
|
||||
// Forward data on to bch adapter if this is the response of a BCH query.
|
||||
try {
|
||||
if (parsedData.payload.result.method === 'bch') {
|
||||
console.log('routing to BCH adapter')
|
||||
retObj = await _this.adapters.bch.rpcHandler(parsedData)
|
||||
}
|
||||
} catch (err) {
|
||||
/* exit quietly */
|
||||
}
|
||||
|
||||
// Route the command to the appropriate route handler.
|
||||
switch (parsedData.payload.method) {
|
||||
case 'users':
|
||||
@@ -116,6 +127,8 @@ class JSONRPC {
|
||||
case 'about':
|
||||
retObj = await _this.aboutController.aboutRouter(parsedData)
|
||||
break
|
||||
case 'bch':
|
||||
retObj = await _this.adapters.bch.rpcHandler(parsedData)
|
||||
// default:
|
||||
// // Process response from REST API.
|
||||
// _this.adapters.ipfs.ipfsCoordAdapter.peerInputHandler(parsedData)
|
||||
|
||||
@@ -489,6 +489,8 @@ class BchRESTControllerLib {
|
||||
* "pubkey": {
|
||||
* "success": true,
|
||||
* "publicKey": "033f267fec0f7eb2b27f8c2e3052b3d03b09d36b47de4082ffb638ffb334ef0eee"
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
@@ -505,6 +507,121 @@ class BchRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {REST} /bch/utxoIsValid utxoIsValid
|
||||
* @apiPermission public
|
||||
* @apiName utxoIsValid
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription Verify if UTXO is valid
|
||||
* Given a UTXO object (txid and vout), a full node is queried to verify that
|
||||
* the UTXO still exists in the mempool (true), or if it has been spent (false).
|
||||
*
|
||||
* - jsonrpc: "" - jsonrpc version
|
||||
* - id: "" - jsonrpc id
|
||||
* - result: {} - Result of the petition with the RPC information
|
||||
* - success: - Request status
|
||||
* - isValid: - Boolean: true or false
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "utxoIsValid", "utxo": {"tx_hash": "17754221b29f189532d4fc2ae89fb467ad2dede30fdec4854eb2129b3ba90d7a", "tx_pos": 0}}}
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* {
|
||||
* "jsonrpc":"2.0",
|
||||
* "id":"555",
|
||||
* "result":{
|
||||
* "method":"bch",
|
||||
* "reciever":"QmU86vLVbUY1UhziKB6rak7GPKRA2QHWvzNm2AjEvXNsT6",
|
||||
* "value":{
|
||||
* "success": true,
|
||||
* "status": 200,
|
||||
* "endpoint": "utxoIsValid",
|
||||
* "isValid": true
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* }
|
||||
*/
|
||||
async utxoIsValid (ctx) {
|
||||
try {
|
||||
const utxo = ctx.request.body.utxo
|
||||
|
||||
const data = await this.adapters.bch.utxoIsValid(utxo)
|
||||
// console.log(`utxoIsValid data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
ctx.body = data
|
||||
} catch (err) {
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {REST} /bch getTokenData
|
||||
* @apiPermission public
|
||||
* @apiName getTokenData
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription Get data associated with a token
|
||||
* Given a token ID, this endpoint will retrieve the IPFS CIDs associated with
|
||||
* the tokens mutable and immutable data. This is extension of the PS002
|
||||
* specification for mutable data for tokens:
|
||||
* https://github.com/Permissionless-Software-Foundation/specifications/blob/master/ps002-slp-mutable-data.md
|
||||
*
|
||||
* - jsonrpc: "" - jsonrpc version
|
||||
* - id: "" - jsonrpc id
|
||||
* - result: {} - Result of the petition with the RPC information
|
||||
* - success: - Request status
|
||||
* - getTokenData: - Address public key
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "getTokenData", "tokenId": "c85042ab08a2099f27de880a30f9a42874202751d834c42717a20801a00aab0d" }}
|
||||
*
|
||||
* @apiSuccessExample {json} Success-Response:
|
||||
* {
|
||||
* "jsonrpc":"2.0",
|
||||
* "id":"555",
|
||||
* "result":{
|
||||
* "method":"bch",
|
||||
* "reciever":"QmU86vLVbUY1UhziKB6rak7GPKRA2QHWvzNm2AjEvXNsT6",
|
||||
* "value":{
|
||||
* "success": true,
|
||||
* "status": 200,
|
||||
* "endpoint": "getTokenData",
|
||||
* "tokenData": {
|
||||
* "genesisData": {
|
||||
* "type": 1,
|
||||
* "ticker": 'MT2',
|
||||
* "name": 'Mutable Token',
|
||||
* "tokenId": 'c85042ab08a2099f27de880a30f9a42874202751d834c42717a20801a00aab0d',
|
||||
* "documentUri": 'ipfs://bafybeie7oxpsr7evcnlptecxfdhaqlot4732phukd2ekgvuzoir2frost4',
|
||||
* "documentHash": '56ed1a5768076a318d02b5db64e125544dca57ab6b2cc7ca61dfa4645d244463',
|
||||
* "decimals": 0,
|
||||
* "mintBatonIsActive": true,
|
||||
* "tokensInCirculationBN": '1000',
|
||||
* "tokensInCirculationStr": '1000',
|
||||
* "blockCreated": 739412,
|
||||
* "totalBurned": '0',
|
||||
* "totalMinted": '1000'
|
||||
* },
|
||||
* "immutableData": 'ipfs://bafybeie7oxpsr7evcnlptecxfdhaqlot4732phukd2ekgvuzoir2frost4',
|
||||
* "mutableData": 'ipfs://bafybeigotuony53ley3n63hqwyxiqruqn5uamskmci6f645putnc46jju4'
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
async getTokenData (ctx) {
|
||||
try {
|
||||
const tokenId = ctx.request.body.tokenId
|
||||
|
||||
const data = await this.adapters.bch.getTokenData(tokenId)
|
||||
console.log(`getTokenData 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.
|
||||
|
||||
@@ -59,6 +59,8 @@ class BchRouter {
|
||||
this.router.post('/txHistory', this.postTxHistory)
|
||||
this.router.post('/txData', this.postTxData)
|
||||
this.router.post('/pubkey', this.postPubKey)
|
||||
this.router.post('/utxoIsValid', this.utxoIsValid)
|
||||
this.router.post('/getTokenData', this.getTokenData)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(this.router.routes())
|
||||
@@ -96,6 +98,14 @@ class BchRouter {
|
||||
async postPubKey (ctx, next) {
|
||||
await _this.bchRESTController.pubKey(ctx, next)
|
||||
}
|
||||
|
||||
async utxoIsValid (ctx, next) {
|
||||
await _this.bchRESTController.utxoIsValid(ctx, next)
|
||||
}
|
||||
|
||||
async getTokenData (ctx, next) {
|
||||
await _this.bchRESTController.getTokenData(ctx, next)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BchRouter
|
||||
|
||||
@@ -14,12 +14,12 @@ class UseCases {
|
||||
'Instance of adapters must be passed in when instantiating Use Cases library.'
|
||||
)
|
||||
}
|
||||
this.eventEmitter = localConfig.eventEmitter
|
||||
if (!this.eventEmitter) {
|
||||
throw new Error(
|
||||
'Instance of Event Emitter must be passed in when instantiating Use Cases library.'
|
||||
)
|
||||
}
|
||||
// this.eventEmitter = localConfig.eventEmitter
|
||||
// if (!this.eventEmitter) {
|
||||
// throw new Error(
|
||||
// 'Instance of Event Emitter must be passed in when instantiating Use Cases library.'
|
||||
// )
|
||||
// }
|
||||
|
||||
// console.log('use-cases/index.js localConfig: ', localConfig)
|
||||
this.user = new UserUseCases(localConfig)
|
||||
|
||||
@@ -54,19 +54,19 @@ describe('#bch-use-case', () => {
|
||||
}
|
||||
})
|
||||
|
||||
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.'
|
||||
)
|
||||
}
|
||||
})
|
||||
// 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', () => {
|
||||
@@ -284,4 +284,64 @@ describe('#bch-use-case', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#utxoIsValid', () => {
|
||||
it('should validate a utxo', async () => {
|
||||
// Force connection to a wallet service
|
||||
uut.ipfs.ipfsCoordAdapter.state = {
|
||||
selectedServiceProvider: 'abc123'
|
||||
}
|
||||
|
||||
// Mock depenencies
|
||||
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
|
||||
|
||||
const utxo = { a: 'b' }
|
||||
|
||||
const result = await uut.utxoIsValid(utxo)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.key, 'value')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
await uut.utxoIsValid()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTokenData', () => {
|
||||
it('should validate a utxo', async () => {
|
||||
// Force connection to a wallet service
|
||||
uut.ipfs.ipfsCoordAdapter.state = {
|
||||
selectedServiceProvider: 'abc123'
|
||||
}
|
||||
|
||||
// Mock depenencies
|
||||
sandbox.stub(uut, 'waitForRPCResponse').resolves({ key: 'value' })
|
||||
|
||||
const tokenId = 'blah'
|
||||
|
||||
const result = await uut.getTokenData(tokenId)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.key, 'value')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
await uut.getTokenData()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -9,7 +9,7 @@ const cloneDeep = require('lodash.clonedeep')
|
||||
const IPFSCoordAdapter = require('../../../src/adapters/ipfs/ipfs-coord')
|
||||
const IPFSMock = require('../mocks/ipfs-mock')
|
||||
const IPFSCoordMock = require('../mocks/ipfs-coord-mock')
|
||||
const EventEmitter = require('events')
|
||||
const config = require('../../../config')
|
||||
const mockDataLib = require('../mocks/adapters/ipfs-coord-mocks')
|
||||
|
||||
describe('#IPFS', () => {
|
||||
@@ -19,7 +19,7 @@ describe('#IPFS', () => {
|
||||
|
||||
beforeEach(() => {
|
||||
const ipfs = IPFSMock.create()
|
||||
uut = new IPFSCoordAdapter({ ipfs, eventEmitter: new EventEmitter() })
|
||||
uut = new IPFSCoordAdapter({ ipfs })
|
||||
|
||||
sandbox = sinon.createSandbox()
|
||||
|
||||
@@ -47,19 +47,19 @@ describe('#IPFS', () => {
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if EventEmitter instance is not included', () => {
|
||||
try {
|
||||
const ipfs = IPFSMock.create()
|
||||
uut = new IPFSCoordAdapter({ ipfs })
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
assert.include(
|
||||
err.message,
|
||||
'An instance of an EventEmitter must be passed when instantiating the ipfs-coord adapter.'
|
||||
)
|
||||
}
|
||||
})
|
||||
// it('should throw an error if EventEmitter instance is not included', () => {
|
||||
// try {
|
||||
// const ipfs = IPFSMock.create()
|
||||
// uut = new IPFSCoordAdapter({ ipfs })
|
||||
//
|
||||
// assert.fail('Unexpected code path')
|
||||
// } catch (err) {
|
||||
// assert.include(
|
||||
// err.message,
|
||||
// 'An instance of an EventEmitter must be passed when instantiating the ipfs-coord adapter.'
|
||||
// )
|
||||
// }
|
||||
// })
|
||||
})
|
||||
|
||||
describe('#start', () => {
|
||||
@@ -86,6 +86,16 @@ describe('#IPFS', () => {
|
||||
|
||||
assert.equal(result, true)
|
||||
})
|
||||
it('should return a promise that resolves into an instance of IPFS in production mode', async () => {
|
||||
uut.config.isProduction = true
|
||||
// Mock dependencies.
|
||||
uut.IpfsCoord = IPFSCoordMock
|
||||
|
||||
const result = await uut.start()
|
||||
// console.log('result: ', result)
|
||||
assert.equal(result, true)
|
||||
config.isProduction = false
|
||||
})
|
||||
})
|
||||
|
||||
describe('#attachRPCRouter', () => {
|
||||
@@ -152,22 +162,22 @@ describe('#IPFS', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#peerInputHandler', () => {
|
||||
it('should emit an event trigger', () => {
|
||||
const data = 'some data'
|
||||
|
||||
uut.peerInputHandler(data)
|
||||
|
||||
assert.isOk(true, 'Not throwing an error is a success')
|
||||
})
|
||||
|
||||
it('should catch and report errors', () => {
|
||||
// Force an error
|
||||
sandbox.stub(uut.eventEmitter, 'emit').throws(new Error('test error'))
|
||||
|
||||
uut.peerInputHandler()
|
||||
|
||||
assert.isOk(true, 'Not throwing an error is a success.')
|
||||
})
|
||||
})
|
||||
// describe('#peerInputHandler', () => {
|
||||
// it('should emit an event trigger', () => {
|
||||
// const data = 'some data'
|
||||
//
|
||||
// uut.peerInputHandler(data)
|
||||
//
|
||||
// assert.isOk(true, 'Not throwing an error is a success')
|
||||
// })
|
||||
//
|
||||
// it('should catch and report errors', () => {
|
||||
// // Force an error
|
||||
// sandbox.stub(uut.eventEmitter, 'emit').throws(new Error('test error'))
|
||||
//
|
||||
// uut.peerInputHandler()
|
||||
//
|
||||
// assert.isOk(true, 'Not throwing an error is a success.')
|
||||
// })
|
||||
// })
|
||||
})
|
||||
|
||||
@@ -4,10 +4,11 @@
|
||||
|
||||
const assert = require('chai').assert
|
||||
const sinon = require('sinon')
|
||||
|
||||
const IPFSLib = require('../../../src/adapters/ipfs/ipfs')
|
||||
const IPFSMock = require('../mocks/ipfs-mock')
|
||||
const config = require('../../../config')
|
||||
|
||||
// config.isProduction = true;
|
||||
describe('#IPFS-adapter', () => {
|
||||
let uut
|
||||
let sandbox
|
||||
@@ -18,7 +19,27 @@ describe('#IPFS-adapter', () => {
|
||||
sandbox = sinon.createSandbox()
|
||||
})
|
||||
|
||||
afterEach(() => sandbox.restore())
|
||||
afterEach(() => {
|
||||
sandbox.restore()
|
||||
})
|
||||
|
||||
describe('#constructor', () => {
|
||||
it('should instantiate IPFS Lib in dev mode.', async () => {
|
||||
const _uut = new IPFSLib()
|
||||
assert.exists(_uut)
|
||||
assert.isFunction(_uut.start)
|
||||
assert.isFunction(_uut.stop)
|
||||
})
|
||||
|
||||
it('should instantiate dev IPFS Lib in production mode.', async () => {
|
||||
config.isProduction = true
|
||||
const _uut = new IPFSLib()
|
||||
assert.exists(_uut)
|
||||
assert.isFunction(_uut.start)
|
||||
assert.isFunction(_uut.stop)
|
||||
config.isProduction = false
|
||||
})
|
||||
})
|
||||
|
||||
describe('#start', () => {
|
||||
it('should return a promise that resolves into an instance of IPFS.', async () => {
|
||||
@@ -33,6 +54,18 @@ describe('#IPFS-adapter', () => {
|
||||
assert.property(result, 'config')
|
||||
})
|
||||
|
||||
it('should return a promise that resolves into an instance of IPFS in production mode.', async () => {
|
||||
// Mock dependencies.
|
||||
uut.IPFS = IPFSMock
|
||||
uut.config.isProduction = true
|
||||
const result = await uut.start()
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(uut.isReady, true)
|
||||
|
||||
assert.property(result, 'config')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
|
||||
@@ -322,4 +322,76 @@ describe('#BCH-REST-Controller', () => {
|
||||
assert.equal(ctx.status, 200)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#utxoIsValid', () => {
|
||||
it('should return 422 status on arbitrary error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.adapters.bch, 'utxoIsValid')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
ctx.request.body = {
|
||||
utxo: 'blah'
|
||||
}
|
||||
|
||||
await uut.utxoIsValid(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, 'utxoIsValid').resolves({ status: 200 })
|
||||
|
||||
ctx.request.body = {
|
||||
utxo: 'blah'
|
||||
}
|
||||
|
||||
await uut.utxoIsValid(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTokenData', () => {
|
||||
it('should return 422 status on arbitrary error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.adapters.bch, 'getTokenData')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
ctx.request.body = {
|
||||
tokenId: 'blah'
|
||||
}
|
||||
|
||||
await uut.getTokenData(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, 'getTokenData').resolves({ status: 200 })
|
||||
|
||||
ctx.request.body = {
|
||||
tokenId: 'blah'
|
||||
}
|
||||
|
||||
await uut.getTokenData(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -93,6 +93,14 @@ class BchUseCaseMock {
|
||||
return {}
|
||||
}
|
||||
|
||||
async utxoIsValid() {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getTokenData() {
|
||||
return {}
|
||||
}
|
||||
|
||||
async waitForRPCResponse () {
|
||||
return {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user