mirror of
https://github.com/Permissionless-Software-Foundation/ipfs-bch-wallet-consumer.git
synced 2026-09-21 16:52:03 -07:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3fc384909e | ||
|
|
9a8d0a6ec1 | ||
|
|
9e14e24eb4 | ||
|
|
1eb68c9ea5 | ||
|
|
c26ad11290 | ||
|
|
00a7b78fde | ||
|
|
9d80341357 | ||
|
|
00b76226ec | ||
|
|
f0552bce32 | ||
|
|
4aa7de253d | ||
|
|
abd01e647a | ||
|
|
8237fec8b7 | ||
|
|
0d06b2eaaa | ||
|
|
82ea90c45c | ||
|
|
fbe7bf9742 | ||
|
|
4447c2224e | ||
|
|
db72e0c95e | ||
|
|
9c9c3e7770 | ||
|
|
2ef0130559 | ||
|
|
610ca4ec8b | ||
|
|
1a32b941df | ||
|
|
433931111c | ||
|
|
3edc945487 | ||
|
|
c66194ceeb | ||
|
|
e35e130816 | ||
|
|
bba4bd5931 | ||
|
|
48d13ed6bc | ||
|
|
d6f9b1b754 |
Vendored
+2
@@ -84,6 +84,8 @@ module.exports = {
|
||||
preferredProvider: process.env.PREFERRED_PROVIDER
|
||||
? process.env.PREFERRED_PROVIDER
|
||||
: '',
|
||||
// If true, users will not be able to change the provider via a REST API call.
|
||||
freezeProvider: process.env.FREEZE_PROVIDER ? true : false,
|
||||
// : 'QmdBGB8S6sEr19gaAxJYjhRbz5ZmMiH5a1JyyMmaxcRKnY'
|
||||
// : 'QmUTx6KqYKVZbKpKxR7vGDUgZFYVvVVyEWDeCYq4GwBCff'
|
||||
// Used for development
|
||||
|
||||
Generated
+31
-30
@@ -9,12 +9,12 @@
|
||||
"version": "1.0.0",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@psf/bch-js": "6.4.5",
|
||||
"@psf/bch-js": "6.7.1",
|
||||
"axios": "0.27.2",
|
||||
"bcryptjs": "2.4.3",
|
||||
"glob": "7.1.6",
|
||||
"ipfs": "0.62.3",
|
||||
"ipfs-coord": "8.0.4",
|
||||
"ipfs-coord": "8.0.8",
|
||||
"ipfs-http-client": "55.0.0",
|
||||
"jsonrpc-lite": "2.2.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
@@ -36,7 +36,7 @@
|
||||
"nodemailer": "6.7.5",
|
||||
"passport-local": "1.0.0",
|
||||
"public-ip": "4.0.4",
|
||||
"semver": "^7.3.5",
|
||||
"semver": "7.3.5",
|
||||
"winston": "3.3.3",
|
||||
"winston-daily-rotate-file": "4.5.0"
|
||||
},
|
||||
@@ -1481,14 +1481,15 @@
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/@psf/bch-js": {
|
||||
"version": "6.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@psf/bch-js/-/bch-js-6.4.5.tgz",
|
||||
"integrity": "sha512-sH72zbJJI5oe9EUMczFQb0jCaNQb8rc3/86E6xXKl3325HCwBqE80BX7JFvZ97Qv1gGxwp/3WROp59v6Dz2NKA==",
|
||||
"version": "6.7.1",
|
||||
"resolved": "http://94.130.170.209:4873/@psf%2fbch-js/-/bch-js-6.7.1.tgz",
|
||||
"integrity": "sha512-B7qGxgXQ3CtIWyGpBOUIMylBe7Y3fAZl10kDgbYIiNeHw/W5q7nhczl5it1FtqigcwekOnETMMhjZdabZ/A+pw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@chris.troutner/bip32-utils": "1.0.5",
|
||||
"@psf/bip21": "2.0.1",
|
||||
"@psf/bitcoincash-ops": "2.0.0",
|
||||
"@psf/bitcoincashjs-lib": "4.0.2",
|
||||
"@psf/bitcoincashjs-lib": "4.0.3",
|
||||
"@psf/coininfo": "4.0.0",
|
||||
"axios": "0.26.1",
|
||||
"bc-bip68": "1.0.5",
|
||||
@@ -1536,9 +1537,9 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@psf/bitcoincashjs-lib": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "http://94.130.170.209:4873/@psf%2fbitcoincashjs-lib/-/bitcoincashjs-lib-4.0.2.tgz",
|
||||
"integrity": "sha512-fTy9mW4H0NkQ+dojGtf+nPduA27F3V2YpBi5licYUVjdVRD/xpUCTgEN1cYRAupOaeDH/AYOWT0MWLkbQSTxAQ==",
|
||||
"version": "4.0.3",
|
||||
"resolved": "http://94.130.170.209:4873/@psf%2fbitcoincashjs-lib/-/bitcoincashjs-lib-4.0.3.tgz",
|
||||
"integrity": "sha512-sJYi7jYUqR7S+Z8TjN3W/5Lcju7xcIBxYLhIEpnOF+jR9kMt0ftpVjQBt8vVsJZUD8ArL9bf59oDETjdOUcdGQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@psf/bitcoincash-ops": "^2.0.0",
|
||||
@@ -8839,9 +8840,10 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ipfs-coord": {
|
||||
"version": "8.0.4",
|
||||
"resolved": "https://registry.npmjs.org/ipfs-coord/-/ipfs-coord-8.0.4.tgz",
|
||||
"integrity": "sha512-jwZsrDlEWSHMnAde8PdyUScTzRlTCIvPAziChoczj5ZOZJIUbxLNkGphBI0xDj2G/LzjPrU1Ko/QZrU+oLFUnw==",
|
||||
"version": "8.0.8",
|
||||
"resolved": "http://94.130.170.209:4873/ipfs-coord/-/ipfs-coord-8.0.8.tgz",
|
||||
"integrity": "sha512-2ivZ7dCIrpS7VBN0DohaGBYRe3bzybkGbl0lOTyNeCUhrrYniC66dAnfdCY8CmpEeXoEYsaW2hLmohz4jDHywQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"axios": "0.21.4",
|
||||
"bch-encrypt-lib": "2.0.0",
|
||||
@@ -18620,10 +18622,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.3.7",
|
||||
"resolved": "http://94.130.170.209:4873/semver/-/semver-7.3.7.tgz",
|
||||
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
|
||||
"license": "ISC",
|
||||
"version": "7.3.5",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
|
||||
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
@@ -23176,14 +23177,14 @@
|
||||
"integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
|
||||
},
|
||||
"@psf/bch-js": {
|
||||
"version": "6.4.5",
|
||||
"resolved": "https://registry.npmjs.org/@psf/bch-js/-/bch-js-6.4.5.tgz",
|
||||
"integrity": "sha512-sH72zbJJI5oe9EUMczFQb0jCaNQb8rc3/86E6xXKl3325HCwBqE80BX7JFvZ97Qv1gGxwp/3WROp59v6Dz2NKA==",
|
||||
"version": "6.7.1",
|
||||
"resolved": "http://94.130.170.209:4873/@psf%2fbch-js/-/bch-js-6.7.1.tgz",
|
||||
"integrity": "sha512-B7qGxgXQ3CtIWyGpBOUIMylBe7Y3fAZl10kDgbYIiNeHw/W5q7nhczl5it1FtqigcwekOnETMMhjZdabZ/A+pw==",
|
||||
"requires": {
|
||||
"@chris.troutner/bip32-utils": "1.0.5",
|
||||
"@psf/bip21": "2.0.1",
|
||||
"@psf/bitcoincash-ops": "2.0.0",
|
||||
"@psf/bitcoincashjs-lib": "4.0.2",
|
||||
"@psf/bitcoincashjs-lib": "4.0.3",
|
||||
"@psf/coininfo": "4.0.0",
|
||||
"axios": "0.26.1",
|
||||
"bc-bip68": "1.0.5",
|
||||
@@ -23230,9 +23231,9 @@
|
||||
"integrity": "sha512-M3PWqRpeJq6rli2NqWGbas76z9TrdGOmNuDFACBWBMctPucEAsFQY2AmyFHRSa7hEwythwvrPh9AG/n6ehmEog=="
|
||||
},
|
||||
"@psf/bitcoincashjs-lib": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "http://94.130.170.209:4873/@psf%2fbitcoincashjs-lib/-/bitcoincashjs-lib-4.0.2.tgz",
|
||||
"integrity": "sha512-fTy9mW4H0NkQ+dojGtf+nPduA27F3V2YpBi5licYUVjdVRD/xpUCTgEN1cYRAupOaeDH/AYOWT0MWLkbQSTxAQ==",
|
||||
"version": "4.0.3",
|
||||
"resolved": "http://94.130.170.209:4873/@psf%2fbitcoincashjs-lib/-/bitcoincashjs-lib-4.0.3.tgz",
|
||||
"integrity": "sha512-sJYi7jYUqR7S+Z8TjN3W/5Lcju7xcIBxYLhIEpnOF+jR9kMt0ftpVjQBt8vVsJZUD8ArL9bf59oDETjdOUcdGQ==",
|
||||
"requires": {
|
||||
"@psf/bitcoincash-ops": "^2.0.0",
|
||||
"@psf/pushdata-bitcoin": "^1.2.2",
|
||||
@@ -28416,9 +28417,9 @@
|
||||
}
|
||||
},
|
||||
"ipfs-coord": {
|
||||
"version": "8.0.4",
|
||||
"resolved": "https://registry.npmjs.org/ipfs-coord/-/ipfs-coord-8.0.4.tgz",
|
||||
"integrity": "sha512-jwZsrDlEWSHMnAde8PdyUScTzRlTCIvPAziChoczj5ZOZJIUbxLNkGphBI0xDj2G/LzjPrU1Ko/QZrU+oLFUnw==",
|
||||
"version": "8.0.8",
|
||||
"resolved": "http://94.130.170.209:4873/ipfs-coord/-/ipfs-coord-8.0.8.tgz",
|
||||
"integrity": "sha512-2ivZ7dCIrpS7VBN0DohaGBYRe3bzybkGbl0lOTyNeCUhrrYniC66dAnfdCY8CmpEeXoEYsaW2hLmohz4jDHywQ==",
|
||||
"requires": {
|
||||
"axios": "0.21.4",
|
||||
"bch-encrypt-lib": "2.0.0",
|
||||
@@ -35492,9 +35493,9 @@
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.3.7",
|
||||
"resolved": "http://94.130.170.209:4873/semver/-/semver-7.3.7.tgz",
|
||||
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
|
||||
"version": "7.3.5",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
|
||||
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
}
|
||||
|
||||
+3
-3
@@ -23,12 +23,12 @@
|
||||
},
|
||||
"repository": "Permissionless-Software-Foundation/ipfs-bch-wallet-consumer",
|
||||
"dependencies": {
|
||||
"@psf/bch-js": "6.4.5",
|
||||
"@psf/bch-js": "6.7.1",
|
||||
"axios": "0.27.2",
|
||||
"bcryptjs": "2.4.3",
|
||||
"glob": "7.1.6",
|
||||
"ipfs": "0.62.3",
|
||||
"ipfs-coord": "8.0.4",
|
||||
"ipfs-coord": "8.0.8",
|
||||
"ipfs-http-client": "55.0.0",
|
||||
"jsonrpc-lite": "2.2.0",
|
||||
"jsonwebtoken": "8.5.1",
|
||||
@@ -50,7 +50,7 @@
|
||||
"nodemailer": "6.7.5",
|
||||
"passport-local": "1.0.0",
|
||||
"public-ip": "4.0.4",
|
||||
"semver": "^7.3.5",
|
||||
"semver": "7.3.5",
|
||||
"winston": "3.3.3",
|
||||
"winston-daily-rotate-file": "4.5.0"
|
||||
},
|
||||
|
||||
@@ -30,4 +30,7 @@ export DBURL=mongodb://localhost:27017/ipfs-service-dev
|
||||
|
||||
export CONSUMER_ENV=production
|
||||
|
||||
# Lock the consumer into a preferred service provider.
|
||||
#export PREFERRED_PROVIDER=12D3KooWEzE3HNH86WCb2Xocu6PgBCnSGUD7myJmuTHvoedMwp7K
|
||||
|
||||
npm start
|
||||
|
||||
@@ -208,6 +208,60 @@ class BchAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
// Query the UTXOs associated with an array of up to 20 addresses.
|
||||
async getUtxosBulk (addrs) {
|
||||
try {
|
||||
// console.log('getUtxosBulk() 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.')
|
||||
}
|
||||
|
||||
// Input validation
|
||||
if (!Array.isArray(addrs)) {
|
||||
throw new Error('addresses parameter must be an array')
|
||||
}
|
||||
if (addrs.length > 20) {
|
||||
throw new Error('addresses parameter must not exceed 20 elements')
|
||||
}
|
||||
|
||||
const rpcData = {
|
||||
endpoint: 'utxosBulk',
|
||||
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)
|
||||
// console.log(`getUtxosBulk() data: ${JSON.stringify(data, null, 2)}`)
|
||||
|
||||
return data
|
||||
} catch (err) {
|
||||
// console.log('createUser() error: ', err)
|
||||
wlogger.error('Error in use-cases/bch.js/getUtxosBulk()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
async broadcast (hex) {
|
||||
try {
|
||||
// Throw an error if this IPFS node has not yet made a connection to a
|
||||
@@ -472,6 +526,50 @@ class BchAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
// Get token icon and other media associated with the token.
|
||||
async getTokenData2 (tokenId, updateCache) {
|
||||
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: 'getTokenData2',
|
||||
tokenId,
|
||||
updateCache
|
||||
}
|
||||
|
||||
// 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('getTokenData2() error: ', err)
|
||||
wlogger.error('Error in adapters/bch.js/getTokenData2()')
|
||||
throw err
|
||||
}
|
||||
}
|
||||
|
||||
// Returns a promise that resolves to data when the RPC response is recieved.
|
||||
async waitForRPCResponse (rpcId) {
|
||||
try {
|
||||
|
||||
@@ -214,6 +214,8 @@ class IpfsCoordAdapter {
|
||||
// providers. This method is called periodically by a timer-interval.
|
||||
pollForBchServices () {
|
||||
try {
|
||||
// console.log('pollForBchServices() polling for BCH service')
|
||||
|
||||
// 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)}`)
|
||||
@@ -255,6 +257,8 @@ class IpfsCoordAdapter {
|
||||
console.log(`---->BCH wallet service selected: ${thisPeer}`)
|
||||
}
|
||||
|
||||
// console.log('preferredProvider: ', _this.config.preferredProvider)
|
||||
|
||||
// If a preferred provider is set in the config file, then connect
|
||||
// to the preferred provider when it's discovered.
|
||||
if (
|
||||
@@ -264,6 +268,8 @@ class IpfsCoordAdapter {
|
||||
_this.state.selectedServiceProvider = thisPeer
|
||||
}
|
||||
|
||||
// console.log('selectedServiceProvider: ', _this.state.selectedServiceProvider)
|
||||
|
||||
// Check if the peer has already been added to the list of providers.
|
||||
const alreadyExists = _this.state.serviceProviders.filter(
|
||||
(x) => x === thisPeer
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*/
|
||||
|
||||
const { wlogger } = require('../../../adapters/wlogger')
|
||||
const config = require('../../../../config')
|
||||
|
||||
// let _this
|
||||
|
||||
@@ -23,10 +24,7 @@ class BchRESTControllerLib {
|
||||
}
|
||||
|
||||
// Encapsulate dependencies
|
||||
// this.UserModel = this.adapters.localdb.Users
|
||||
// this.userUseCases = this.useCases.user
|
||||
|
||||
// _this = this
|
||||
this.config = config
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,6 +64,11 @@ class BchRESTControllerLib {
|
||||
try {
|
||||
const providerId = ctx.request.body.provider
|
||||
|
||||
// Throw an error, if the provider has been set by an environment variable.
|
||||
if (this.config.freezeProvider) {
|
||||
throw new Error('Consumer has preferredProvider set in environment variable. Refusing to switch providers.')
|
||||
}
|
||||
|
||||
await this.adapters.bch.selectProvider(providerId)
|
||||
|
||||
const body = {
|
||||
@@ -231,6 +234,103 @@ class BchRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /bch/utxosBulk UTXOs Bulk
|
||||
* @apiName UTXOs Bulk
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription This endpoint returns UTXOs held at an address, hydrated
|
||||
* with token information.
|
||||
* @apiDescription This endpoint is the same as the /bch/utxos endpoint, but
|
||||
* it allows an array of up to 20 addresses to be queried at the same time.
|
||||
* This reduces the number of JSON RPC calls, and is very handy
|
||||
* for HD wallets that need to quickly scan a lot of addresses.
|
||||
*
|
||||
* Given an address, this endpoint will return an object with thre following
|
||||
* properties:
|
||||
*
|
||||
* - addresses: [] - the addresses 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
|
||||
* [
|
||||
* {
|
||||
* "addresses": ["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 utxosBulk (ctx) {
|
||||
try {
|
||||
const addrs = ctx.request.body.addresses
|
||||
// console.log('address: ', address)
|
||||
|
||||
const utxos = await this.adapters.bch.getUtxosBulk(addrs)
|
||||
// console.log(`utxos: ${JSON.stringify(utxos, null, 2)}`)
|
||||
|
||||
ctx.body = utxos
|
||||
} catch (err) {
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {post} /bch/broadcast Broadcast
|
||||
* @apiName Broadcast
|
||||
@@ -556,59 +656,21 @@ class BchRESTControllerLib {
|
||||
}
|
||||
|
||||
/**
|
||||
* @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'
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*/
|
||||
* @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
|
||||
*
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "tokenId": "43eddfb11c9941edffb8c8815574bb0a43969a7b1de39ad14cd043eaa24fd38d", "withTxHistory": true }' https://bc01-ca-bch-consumer.fullstackcash.nl/bch/getTokenData
|
||||
*/
|
||||
async getTokenData (ctx) {
|
||||
try {
|
||||
const tokenId = ctx.request.body.tokenId
|
||||
@@ -623,6 +685,44 @@ class BchRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @api {REST} /bch getTokenData2
|
||||
* @apiPermission public
|
||||
* @apiName getTokenData2
|
||||
* @apiGroup REST BCH
|
||||
* @apiDescription Get token icon and other media
|
||||
*
|
||||
* Get the icon for a token, given it's token ID.
|
||||
* This function expects a string input of a token ID property.
|
||||
* This function returns an object with a tokenIcon property that contains
|
||||
* the URL to the icon.
|
||||
*
|
||||
* The output object always have these properties:
|
||||
* - tokenIcon: A url to the token icon, if it exists.
|
||||
* - tokenStats: Data about the token from psf-slp-indexer.
|
||||
* - optimizedTokenIcon: An alternative, potentially more optimal, url to the token icon, if it exists.
|
||||
* - iconRepoCompatible: true if the token icon is available via token.bch.sx
|
||||
* - ps002Compatible: true if the token icon is compatible with PS007 specification.
|
||||
*
|
||||
* @apiExample Example usage:
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "tokenId": "43eddfb11c9941edffb8c8815574bb0a43969a7b1de39ad14cd043eaa24fd38d" }' https://bc01-ca-bch-consumer.fullstackcash.nl/bch/getTokenData2
|
||||
* curl -H "Content-Type: application/json" -X POST -d '{ "tokenId": "43eddfb11c9941edffb8c8815574bb0a43969a7b1de39ad14cd043eaa24fd38d", "updateCache": true }' https://bc01-ca-bch-consumer.fullstackcash.nl/bch/getTokenData2
|
||||
*/
|
||||
async getTokenData2 (ctx) {
|
||||
try {
|
||||
const tokenId = ctx.request.body.tokenId
|
||||
const updateCache = ctx.request.body.updateCache
|
||||
console.log('getTokenData2() updateCache: ', updateCache)
|
||||
|
||||
const data = await this.adapters.bch.getTokenData2(tokenId, updateCache)
|
||||
console.log(`getTokenData2 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.
|
||||
|
||||
@@ -55,12 +55,14 @@ class BchRouter {
|
||||
this.router.post('/provider', this.postProvider)
|
||||
this.router.post('/balance', this.postBalance)
|
||||
this.router.post('/utxos', this.postUtxos)
|
||||
this.router.post('/utxosBulk', this.postUtxosBulk)
|
||||
this.router.post('/broadcast', this.postBroadcast)
|
||||
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)
|
||||
this.router.post('/getTokenData2', this.getTokenData2)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(this.router.routes())
|
||||
@@ -83,6 +85,10 @@ class BchRouter {
|
||||
await _this.bchRESTController.utxos(ctx, next)
|
||||
}
|
||||
|
||||
async postUtxosBulk (ctx, next) {
|
||||
await _this.bchRESTController.utxosBulk(ctx, next)
|
||||
}
|
||||
|
||||
async postBroadcast (ctx, next) {
|
||||
await _this.bchRESTController.broadcast(ctx, next)
|
||||
}
|
||||
@@ -106,6 +112,10 @@ class BchRouter {
|
||||
async getTokenData (ctx, next) {
|
||||
await _this.bchRESTController.getTokenData(ctx, next)
|
||||
}
|
||||
|
||||
async getTokenData2 (ctx, next) {
|
||||
await _this.bchRESTController.getTokenData2(ctx, next)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = BchRouter
|
||||
|
||||
@@ -64,6 +64,31 @@ class PriceRESTControllerLib {
|
||||
}
|
||||
}
|
||||
|
||||
// Get the XEC price.
|
||||
// TODO: Add API docs.
|
||||
async getXecPrice (ctx) {
|
||||
try {
|
||||
// Request options
|
||||
const opt = {
|
||||
method: 'get',
|
||||
baseURL: 'https://api.coinex.com',
|
||||
url: '/v1/market/ticker?market=xecusdt',
|
||||
timeout: 15000
|
||||
}
|
||||
//
|
||||
|
||||
const response = await this.axios.request(opt)
|
||||
console.log(`response.data: ${JSON.stringify(response.data, null, 2)}`)
|
||||
|
||||
ctx.body = { usd: Number(response.data.data.ticker.last) }
|
||||
} catch (err) {
|
||||
// Write out error to error log.
|
||||
wlogger.error('Error in GET /price/xecusd.', err)
|
||||
|
||||
this.handleError(ctx, err)
|
||||
}
|
||||
}
|
||||
|
||||
// DRY error handler
|
||||
handleError (ctx, err) {
|
||||
// If an HTTP status is specified by the buisiness logic, use that.
|
||||
|
||||
@@ -52,6 +52,7 @@ class PriceRouter {
|
||||
|
||||
// Define the routes and attach the controller.
|
||||
this.router.get('/usd', this.getPrice)
|
||||
this.router.get('/xecusd', this.getXecPrice)
|
||||
|
||||
// Attach the Controller routes to the Koa app.
|
||||
app.use(this.router.routes())
|
||||
@@ -61,6 +62,10 @@ class PriceRouter {
|
||||
async getPrice (ctx, next) {
|
||||
await _this.priceRESTController.getUSD(ctx, next)
|
||||
}
|
||||
|
||||
async getXecPrice (ctx, next) {
|
||||
await _this.priceRESTController.getXecPrice(ctx, next)
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = PriceRouter
|
||||
|
||||
@@ -1,3 +0,0 @@
|
||||
/key/swarm/psk/1.0.0/
|
||||
/base16/
|
||||
bbd935b70105b03ebd0c6a3c2d2730cd22fc0d18c490ccf689b6c8a22e1bed2a
|
||||
@@ -165,6 +165,82 @@ describe('#bch-use-case', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getUtxos', () => {
|
||||
it('should get the UTXOs 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 addr = 'addr'
|
||||
|
||||
const result = await uut.getUtxos(addr)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.key, 'value')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
await uut.getUtxos()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getUtxosBulk', () => {
|
||||
it('should get the UTXOs 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 = ['addr']
|
||||
|
||||
const result = await uut.getUtxosBulk(addrs)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.key, 'value')
|
||||
})
|
||||
|
||||
it('should throw an error if addresses is not an array', async () => {
|
||||
try {
|
||||
await uut.getUtxosBulk()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'addresses parameter must be an array')
|
||||
}
|
||||
})
|
||||
|
||||
it('should throw an error if addresses array is larger than 20 elements', async () => {
|
||||
try {
|
||||
const addrs = []
|
||||
for (let i = 0; i < 25; i++) {
|
||||
addrs.push(i)
|
||||
}
|
||||
|
||||
await uut.getUtxosBulk(addrs)
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'addresses parameter must not exceed 20 elements')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#broadcast', () => {
|
||||
it('should broadcast a transaction', async () => {
|
||||
// Force connection to a wallet service
|
||||
@@ -344,4 +420,34 @@ describe('#bch-use-case', () => {
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTokenData2', () => {
|
||||
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.getTokenData2(tokenId)
|
||||
// console.log('result: ', result)
|
||||
|
||||
assert.equal(result.key, 'value')
|
||||
})
|
||||
|
||||
it('should catch and throw an error', async () => {
|
||||
try {
|
||||
await uut.getTokenData2()
|
||||
|
||||
assert.fail('Unexpected code path')
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
assert.equal(err.message, 'test error')
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -177,6 +177,42 @@ describe('#BCH-REST-Controller', () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe('#utxosBulk', () => {
|
||||
it('should return 422 status on arbitrary error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.adapters.bch, 'getUtxosBulk')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
ctx.request.body = {
|
||||
address: 'blah'
|
||||
}
|
||||
|
||||
await uut.utxosBulk(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, 'getUtxosBulk').resolves({ status: 200 })
|
||||
|
||||
ctx.request.body = {
|
||||
address: 'blah'
|
||||
}
|
||||
|
||||
await uut.utxosBulk(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#broadcast', () => {
|
||||
it('should return 422 status on arbitrary error', async () => {
|
||||
try {
|
||||
@@ -394,4 +430,40 @@ describe('#BCH-REST-Controller', () => {
|
||||
assert.equal(ctx.status, 200)
|
||||
})
|
||||
})
|
||||
|
||||
describe('#getTokenData2', () => {
|
||||
it('should return 422 status on arbitrary error', async () => {
|
||||
try {
|
||||
// Force an error
|
||||
sandbox
|
||||
.stub(uut.adapters.bch, 'getTokenData2')
|
||||
.rejects(new Error('test error'))
|
||||
|
||||
ctx.request.body = {
|
||||
tokenId: 'blah'
|
||||
}
|
||||
|
||||
await uut.getTokenData2(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, 'getTokenData2').resolves({ status: 200 })
|
||||
|
||||
ctx.request.body = {
|
||||
tokenId: 'blah'
|
||||
}
|
||||
|
||||
await uut.getTokenData2(ctx)
|
||||
|
||||
// Assert the expected HTTP response
|
||||
assert.equal(ctx.status, 200)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -93,6 +93,10 @@ class BchUseCaseMock {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getUtxosBulk () {
|
||||
return {}
|
||||
}
|
||||
|
||||
async broadcast () {
|
||||
return {}
|
||||
}
|
||||
@@ -117,6 +121,10 @@ class BchUseCaseMock {
|
||||
return {}
|
||||
}
|
||||
|
||||
async getTokenData2() {
|
||||
return {}
|
||||
}
|
||||
|
||||
async waitForRPCResponse () {
|
||||
return {}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user