Compare commits

..
8 Commits
10 changed files with 770 additions and 23 deletions
+2
View File
@@ -4,6 +4,8 @@
This is a censorship-resistant, IPFS-based microservice that provides access for wallets to access the Bitcoin Cash (BCH) blockchain. It leverages [bch-js](https://github.com/Permissionless-Software-Foundation/bch-js).
- [Decentralized Blockchain Service Providers](https://youtu.be/m_33rRXEats) YouTube video demos this software.
## About This Repository
This repository is forked from the [ipfs-service-provider](https://github.com/Permissionless-Software-Foundation/ipfs-service-provider) repository. That code has been customized to provide BCH blockchain access for wallets. This server-side node.js app provides both REST API over HTTP and JSON RPC over IPFS endpoints for wallets to access the blockchain through.
+1 -1
View File
@@ -43,7 +43,7 @@ module.exports = {
isCircuitRelay: process.env.ENABLE_CIRCUIT_RELAY ? true : false,
// Information passed to other IPFS peers about this node.
apiInfo: 'https://ipfs-service-provider.fullstack.cash/',
apiInfo: 'https://ipfs-bch-wallet-service.fullstack.cash/',
ipfsCoordName: ipfsCoordName,
+75
View File
@@ -8,6 +8,7 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@open-rpc/client-js": "^1.7.0",
"@psf/bch-js": "^4.20.2",
"axios": "^0.21.1",
"bcryptjs": "^2.4.3",
@@ -1670,6 +1671,17 @@
"@octokit/openapi-types": "^6.0.0"
}
},
"node_modules/@open-rpc/client-js": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@open-rpc/client-js/-/client-js-1.7.0.tgz",
"integrity": "sha512-cRGJbXTgdhJNU49vWzJIATRmKBLP2x6tuHJzX9Jg3N8f1VEkge0riUEek2LFIrZiM4TdUp8XV4Ns1W0SZzdfSw==",
"dependencies": {
"isomorphic-fetch": "^3.0.0",
"isomorphic-ws": "^4.0.1",
"strict-event-emitter-types": "^2.0.0",
"ws": "^7.0.0"
}
},
"node_modules/@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
@@ -12810,6 +12822,23 @@
"node": ">=10"
}
},
"node_modules/isomorphic-fetch": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz",
"integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==",
"dependencies": {
"node-fetch": "^2.6.1",
"whatwg-fetch": "^3.4.1"
}
},
"node_modules/isomorphic-ws": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz",
"integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==",
"peerDependencies": {
"ws": "*"
}
},
"node_modules/isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
@@ -24716,6 +24745,11 @@
"node": ">=10"
}
},
"node_modules/strict-event-emitter-types": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strict-event-emitter-types/-/strict-event-emitter-types-2.0.0.tgz",
"integrity": "sha512-Nk/brWYpD85WlOgzw5h173aci0Teyv8YdIAEtV+N88nDB0dLlazZyJMIsN6eo1/AR61l+p6CJTG1JIyFaoNEEA=="
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -25812,6 +25846,11 @@
"@zxing/text-encoding": "0.9.0"
}
},
"node_modules/whatwg-fetch": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz",
"integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA=="
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -27586,6 +27625,17 @@
"@octokit/openapi-types": "^6.0.0"
}
},
"@open-rpc/client-js": {
"version": "1.7.0",
"resolved": "https://registry.npmjs.org/@open-rpc/client-js/-/client-js-1.7.0.tgz",
"integrity": "sha512-cRGJbXTgdhJNU49vWzJIATRmKBLP2x6tuHJzX9Jg3N8f1VEkge0riUEek2LFIrZiM4TdUp8XV4Ns1W0SZzdfSw==",
"requires": {
"isomorphic-fetch": "^3.0.0",
"isomorphic-ws": "^4.0.1",
"strict-event-emitter-types": "^2.0.0",
"ws": "^7.0.0"
}
},
"@protobufjs/aspromise": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz",
@@ -37019,6 +37069,21 @@
"resolved": "https://registry.npmjs.org/iso-url/-/iso-url-0.4.7.tgz",
"integrity": "sha512-27fFRDnPAMnHGLq36bWTpKET+eiXct3ENlCcdcMdk+mjXrb2kw3mhBUg1B7ewAC0kVzlOPhADzQgz1SE6Tglog=="
},
"isomorphic-fetch": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/isomorphic-fetch/-/isomorphic-fetch-3.0.0.tgz",
"integrity": "sha512-qvUtwJ3j6qwsF3jLxkZ72qCgjMysPzDfeV240JHiGZsANBYd+EEuu35v7dfrJ9Up0Ak07D7GGSkGhCHTqg/5wA==",
"requires": {
"node-fetch": "^2.6.1",
"whatwg-fetch": "^3.4.1"
}
},
"isomorphic-ws": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/isomorphic-ws/-/isomorphic-ws-4.0.1.tgz",
"integrity": "sha512-BhBvN2MBpWTaSHdWRb/bwdZJ1WaehQ2L1KngkCkfLUGF0mAWAT1sQUQacEmQ0jXkFw/czDXPNQSL5u2/Krsz1w==",
"requires": {}
},
"isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
@@ -46632,6 +46697,11 @@
"resolved": "https://registry.npmjs.org/streaming-iterables/-/streaming-iterables-5.0.4.tgz",
"integrity": "sha512-nEs6hBGIPsVz6uq6pscGGKfoPDQWrDQW0b0UHurtSDysekfKLmkPg7FQVRE2sj3Rad6yUo9E1sGTxOWyYsHQ/g=="
},
"strict-event-emitter-types": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/strict-event-emitter-types/-/strict-event-emitter-types-2.0.0.tgz",
"integrity": "sha512-Nk/brWYpD85WlOgzw5h173aci0Teyv8YdIAEtV+N88nDB0dLlazZyJMIsN6eo1/AR61l+p6CJTG1JIyFaoNEEA=="
},
"string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
@@ -47538,6 +47608,11 @@
"@zxing/text-encoding": "0.9.0"
}
},
"whatwg-fetch": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz",
"integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA=="
},
"which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+252
View File
@@ -88,10 +88,49 @@ class BCHRPC {
* @apiName Transactions
* @apiGroup JSON BCH
* @apiDescription This endpoint wraps the bchjs.Electrumx.transactions([]) function.
* Given the 'addresses' property this endpoint returns an object with the following properties
*
* - jsonrpc: "" - jsonrpc version
* - id: "" - jsonrpc id
* - result: {} - Result of the petition with the RPC information
* - method: "" - Method used in the petition
* - receiver: "" - Receiver address
* - value: {} - Final result value of the petition
* - success : - Petition status
* - transactions : [] - Transactions of the provided adresses
* - transactions: [] - Transaction details
* - height : - Reference to the blockchain size
* - tx_hash: "" - Hash of the transaction
* - address : "" - Address asociated to the transactions
* - status: - HTTP Status Code
*
* @apiExample Example usage:
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "transactions", "addresses": ["bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"]}}
*
* @apiSuccessExample {json} Success-Response:
* {
* "jsonrpc":"2.0",
* "id":"555",
* "result":{
* "method":"bch",
* "reciever":"QmU86vLVbUY1UhziKB6rak7GPKRA2QHWvzNm2AjEvXNsT6",
* "value":{
* "success":true,
* "transactions":[
* {
* "transactions":[
* {
* "height":631219,
* "tx_hash":"ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f"
* }
* ],
* "address":"bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"
* }
* ],
* "status":200
* }
* }
* }
*/
async transactions (rpcData) {
try {
@@ -127,9 +166,48 @@ class BCHRPC {
* @apiGroup JSON BCH
* @apiDescription This endpoint wraps the bchjs.Electrumx.balance([]) function.
*
* Given the 'addresses' property it returns an object
* with the following properties
*
* - jsonrpc: "" - jsonrpc version
* - id: "" - jsonrpc id
* - result: {} - Result of the petition with the RPC information
* - method: "" - Method used in the petition
* - receiver: "" - Receiver address
* - value: {} - Final result value of the petition
* - success : - Petition status
* - balances : [] - Balance of the provided addresses
* - balance : {} - Object with the balance types of an address
* - confirmed : - Confirmed balance
* - unconfirmed : - Unconfirmed balance
* - address : "" - Address related to the balance
* - status: - HTTP Status Code
*
* @apiExample Example usage:
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "balance", "addresses": ["bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"]}}
*
* @apiSuccessExample {json} Success-Response:
* {
* "jsonrpc":"2.0",
* "id":"555",
* "result":{
* "method":"bch",
* "reciever":"QmU86vLVbUY1UhziKB6rak7GPKRA2QHWvzNm2AjEvXNsT6",
* "value":{
* "success":true,
* "balances":[
* {
* "balance":{
* "confirmed":1000,
* "unconfirmed":0
* },
* "address":"bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"
* }
* ],
* "status":200
* }
* }
* }
*/
async balance (rpcData) {
try {
@@ -167,9 +245,82 @@ class BCHRPC {
* endpoint returns UTXOs held at an address, hydrated
* with token information.
*
* Given an address this endpoint will return an object
* with the following properties
*
* - jsonrpc: "" - jsonrpc version
* - id: "" - jsonrpc id
* - result: {} - Result of the petition with the RPC information
* - method: "" - Method used in the petition
* - receiver: "" - Receiver address
* - value: [] - Final result value of the petition
* - 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:
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "utxos", "address": "bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"}}
*
* @apiSuccessExample {json} Success-Response:
*
* {
* "jsonrpc":"2.0",
* "id":"555",
* "result":{
* "method":"bch",
* "reciever":"QmU86vLVbUY1UhziKB6rak7GPKRA2QHWvzNm2AjEvXNsT6",
* "value":[
* {
* "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":[
*
* ]
* }
* }
* }
* ]
* }
* }
*
*/
async utxos (rpcData) {
try {
@@ -206,9 +357,27 @@ class BCHRPC {
* @apiDescription Broadcast a transaction to the BCH network.
* The transaction should be encoded as a hexidecimal string.
*
* This endpoint will return an object with the following properties
*
* - jsonrpc: "" - jsonrpc version
* - id: "" - jsonrpc id
* - result: {} - Result of the petition with the RPC information
* - method: "" - Method used in the petition
* - receiver: "" - Receiver address
* - value: "" - Final result value of the petition
*
* @apiExample Example usage:
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "broadcast", "hex": "01000000010000000000000000000000000000000000000000000000000000000000000000ffffffff0704ffff001d0104ffffffff0100f2052a0100000043410496b538e853519c726a2c91e61ec11600ae1390813a627c66fb8be7947be63c52da7589379515d4e0a604f8141781e62294721166bf621e73a82cbf2342c858eeac00000000"}}
*
* @apiSuccessExample {json} Success-Response:
*
* "jsonrpc":"2.0",
* "id":"555",
* "result":{
* "method":"bch",
* "reciever":"QmU86vLVbUY1UhziKB6rak7GPKRA2QHWvzNm2AjEvXNsT6",
* "value": "951299775f68a599b95239bfc385423f87a33e11747c299a22ef9dcf3d1557ec"
*
*/
async broadcast (rpcData) {
try {
@@ -243,10 +412,93 @@ class BCHRPC {
* @apiName Transaction
* @apiGroup JSON BCH
* @apiDescription Get data about a specific transaction.
* Given a transaction 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
* - method: "" - Method used in the petition
* - receiver: "" - Receiver address
* - value: {} - Final result value of the petition
* - 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
* - status: - HTTP Status Code
*
* @apiExample Example usage:
* {"jsonrpc":"2.0","id":"555","method":"bch","params":{ "endpoint": "transaction", "txid": "01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b"}}
*
* @apiSuccessExample {json} Success-Response:
* {
* "jsonrpc":"2.0",
* "id":"555",
* "result":{
* "method":"bch",
* "reciever":"QmU86vLVbUY1UhziKB6rak7GPKRA2QHWvzNm2AjEvXNsT6",
* "value":{
* "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":98000,
* "time":1568338904,
* "blocktime":1568338904,
* "isValidSLPTx":false,
* "status":200
* }
* }
* }
*/
async transaction (rpcData) {
try {
+2 -2
View File
@@ -45,8 +45,8 @@ class JSONRPC {
// which controller to route the instruction to.
async router (str, from) {
try {
// console.log('router str: ', str)
// console.log('router from: ', from)
console.log('router str: ', str)
console.log('router from: ', from)
// Exit quietly if 'from' is not specified.
if (!from || typeof from !== 'string') {
+177 -20
View File
@@ -29,20 +29,42 @@ class BCHRESTController {
}
/**
*
* @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,
* data: <data>
* "success":true,
* "transactions":[
* {
* "transactions":[
* {
* "height":631219,
* "tx_hash":"ae2daa01c8172545b5edd205ea438706bcb74e63d4084a26b9ff2a46d46dc97f"
* }
* ],
* "address":"bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"
* }
* ]
* }
*
* @apiError UnprocessableEntity Missing required parameters
@@ -54,6 +76,7 @@ class BCHRESTController {
* "error": "Unprocessable Entity"
* }
*/
async transactions (ctx) {
try {
const addrs = ctx.request.body.addresses
@@ -71,7 +94,19 @@ class BCHRESTController {
* @api {post} /bch/balance Balance
* @apiName Balance
* @apiGroup REST BCH
* @apiDescription This endpoint returns the balance in BCH for an address.
* @apiDescription Devuelve el balance de un address o un array de adresses.
*
* Given the 'addresses' property returns an array of objects
* with the following properties
*
* - success : - Petition status
* - balances : [] - Balance of the provided addresses
* - balance : {} - Object with the balance types of an address
* - confirmed : - Confirmed balance
* - unconfirmed : - Unconfirmed Balance
* - address : "" - Address related to the balance
*
* 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/balance
@@ -79,8 +114,16 @@ class BCHRESTController {
* @apiSuccessExample {json} Success-Response:
* HTTP/1.1 200 OK
* {
* success:true,
* data: <data>
* "success":true,
* "balances":[
* {
* "balance":{
* "confirmed":1000,
* "unconfirmed":0
* },
* "address":"bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj"
* }
* ]
* }
*
* @apiError UnprocessableEntity Missing required parameters
@@ -110,17 +153,70 @@ class BCHRESTController {
* @apiName UTXOs
* @apiGroup REST BCH
* @apiDescription This endpoint returns UTXOs held at an address, hydrated
* with token information.
* 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
* {
* success:true,
* data: <data>
* }
* [
* {
* "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
*
@@ -151,15 +247,14 @@ class BCHRESTController {
* @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
* {
* success:true,
* data: <data>
* }
* "951299775f68a599b95239bfc385423f87a33e11747c299a22ef9dcf3d1557ec"
*
* @apiError UnprocessableEntity Missing required parameters
*
@@ -175,7 +270,7 @@ class BCHRESTController {
const hex = ctx.request.body.hex
const txid = await _this.bchjs.RawTransactions.sendRawTransaction(hex)
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
// console.log(`txid: ${JSON.stringify(txid, null, 2)}`)
ctx.body = txid
} catch (err) {
@@ -188,16 +283,78 @@ class BCHRESTController {
* @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
* {
* success:true,
* data: <data>
* }
* {
* "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
*
+11
View File
@@ -0,0 +1,11 @@
# Consumer Simulation E2E Test
The files in this directory simulates a consumer of web services, using JSON RPC over IPFS. It starts a new IPFS node that connects to the ipfs-bch-wallet-service and exercises each JSON RPC endpoint.
## Instructions
Execute these steps in order to run the test:
1. Run your local copy of ipfs-bch-wallet-service service provider.
2. Update the [test-data.json](./test-data.json) file with the IPFS ID of the ipfs-bch-wallet-service.
3. In another terminal, run the client simulation with `node client.e2e.js`
+29
View File
@@ -0,0 +1,29 @@
/*
An end-to-end (e2e) test simulating a client connecting over IPFS and
that exercises each JSON RPC endpoint.
*/
const TestUtils = require('./lib/test-utils')
const testUtils = new TestUtils()
const testData = require('./test-data.json')
async function startTest () {
try {
console.log('E2E TEST: Starting consumer E2E tests...')
await testUtils.startIpfs()
await testUtils.connectToUut(testData.uutAddr, testData.uutId)
await testUtils.testBalance(testData.uutId)
await testUtils.testTransaction(testData.uutId)
console.log('E2E TEST: Tests completed successfully!')
process.exit()
} catch (err) {
console.error('Error in startTest(): ', err)
}
}
startTest()
+217
View File
@@ -0,0 +1,217 @@
/*
A class library of test utility functions.
*/
// Public npm libraries
const BCHJS = require('@psf/bch-js')
const IpfsCoord = require('ipfs-coord')
const IPFS = require('ipfs')
const EventEmitter = require('events')
const { v4: uid } = require('uuid')
const jsonrpc = require('jsonrpc-lite')
let _this
class TestUtils {
constructor (localConfig = {}) {
// Encapsulate dependencies.
this.bchjs = new BCHJS()
this.eventEmitter = new EventEmitter()
this.uid = uid
this.jsonrpc = jsonrpc
_this = this
}
// This handler function recieves data from other ipfs-coord peers, like
// the ipfs-bch-wallet-service that we're testing.
// It emits the data as an 'rpcData' event. The tests will listen for this
// event.
rpcHandler (inData) {
try {
// console.log('Data recieved by rpcHandler: ', inData)
const jsonData = JSON.parse(inData)
_this.eventEmitter.emit('rpcData', jsonData)
} catch (err) {
console.error('Error in rpcHandler: ', err)
// Do not throw an error. This is a top-level function.
}
}
// Send the RPC command to the service, wait a period of time for a response.
// Timeout if a response is not recieved.
async sendRPC (ipfsId, cmdStr) {
try {
// Send the RPC command to the server/service.
await this.ipfsCoord.ipfs.orbitdb.sendToDb(ipfsId, cmdStr)
let retData
// This event is triggered when the response comes back.
this.eventEmitter.on('rpcData', inData => {
retData = inData
})
// Used for calculating the timeout.
const start = new Date()
let now = start
let timeDiff = 0
// Wait for the response from the server. Exit once the response is
// recieved, or a timeout occurs.
do {
await this.bchjs.Util.sleep(1000)
now = new Date()
timeDiff = now.getTime() - start.getTime()
// console.log('timeDiff: ', timeDiff)
} while (
// Exit once the RPC data comes back, or if a period of time passes.
!retData || // eslint-disable-line no-unmodified-loop-condition
timeDiff > 10000
)
return retData
} catch (err) {
console.error('Error in sendRPC')
throw err
}
}
async startIpfs () {
try {
// Start the IPFS node.
this.ipfs = await IPFS.create()
await this.ipfs.config.profiles.apply('server')
// Start ipfs-coord.
this.ipfsCoord = new IpfsCoord({
ipfs: this.ipfs,
type: 'node.js',
// type: 'browser',
bchjs: this.bchjs,
privateLog: this.rpcHandler, // Default to console.log
isCircuitRelay: false,
apiInfo: 'none',
announceJsonLd: announceJsonLd
})
await this.ipfsCoord.ipfs.start()
await this.ipfsCoord.isReady()
// Wait to let ipfs-coord connect to subnet peers.
await this.bchjs.Util.sleep(30000)
} catch (err) {
console.error('Error in startIpfs().')
throw err
}
}
// Connect to the unit under test (uut)
async connectToUut (addr, ipfsId) {
try {
try {
await this.ipfs.swarm.connect(addr)
console.log(`E2E TEST: Connected to IPFS node ${addr}`)
} catch (err) {
throw new Error('Could not connect to UUT IPFS node.')
}
// console.log(
// 'ipfs-coord peer info: ',
// this.ipfsCoord.ipfs.peers.state.peers[ipfsId]
// )
if (!this.ipfsCoord.ipfs.peers.state.peers[ipfsId]) {
throw new Error('Could not find UUT in ipfs-coord list of peers.')
}
} catch (err) {
console.error('Error in connectToUut()')
console.log(
'Is the service running? Did you update the test-data.json file?'
)
process.exit()
}
}
// Get the balance for a BCH address over JSON RPC.
async testBalance (ipfsId) {
try {
// Generate the JSON RPC command
const id = uid()
const cmd = jsonrpc.request(id, 'bch', {
endpoint: 'balance',
addresses: ['bitcoincash:qrl2nlsaayk6ekxn80pq0ks32dya8xfclyktem2mqj']
})
const cmdStr = JSON.stringify(cmd)
// console.log(`Publishing message to ${ipfsId}`)
console.log('E2E TEST: Sending Balance command...')
const result = await this.sendRPC(ipfsId, cmdStr)
// console.log('result: ', result)
if (result.result.value.success && result.result.value.balances) {
console.log('E2E TEST: balance test passed.')
return true
} else {
console.log('E2E TEST: balance test failed.')
this.failTest()
}
} catch (err) {
console.error('Error in testBalance()')
throw err
}
}
// Get the info a on transaction over JSON RPC.
async testTransaction (ipfsId) {
try {
// Generate the JSON RPC command
const id = uid()
const cmd = jsonrpc.request(id, 'bch', {
endpoint: 'transaction',
txid: '01517ff1587fa5ffe6f5eb91c99cf3f2d22330cd7ee847e928ce90ca95bf781b'
})
const cmdStr = JSON.stringify(cmd)
// console.log(`Publishing message to ${ipfsId}`)
console.log('E2E TEST: Sending Transaction command...')
const result = await this.sendRPC(ipfsId, cmdStr)
// console.log('result: ', result)
if (result.result.value.status && result.result.value.confirmations) {
console.log('E2E TEST: transaction test passed.')
return true
} else {
console.log('E2E TEST: transaction test failed.')
this.failTest()
}
} catch (err) {
console.error('Error in testTransaction()')
throw err
}
}
failTest () {
console.log('Exiting test program.')
process.exit()
}
}
const announceJsonLd = {
'@context': 'https://schema.org/',
'@type': 'WebAPI',
name: 'e2e-test-client',
description: 'A test client runing e2e tests on ipfs-bch-wallet-service.',
documentation: 'https://ipfs-bch-wallet-service.fullstack.cash/',
provider: {
'@type': 'Organization',
name: 'Permissionless Software Foundation',
url: 'https://PSFoundation.cash'
}
}
module.exports = TestUtils
+4
View File
@@ -0,0 +1,4 @@
{
"uutAddr": "/ip4/127.0.0.1/tcp/5668/p2p/QmZSyLnRQWMBknVNjUroLQnrcmDnUVAUU4pMQ2LGhh6b9v",
"uutId": "QmZSyLnRQWMBknVNjUroLQnrcmDnUVAUU4pMQ2LGhh6b9v"
}