Compare commits

..
9 Commits
9 changed files with 295 additions and 22 deletions
+16 -15
View File
@@ -9,7 +9,7 @@
"version": "1.0.0",
"license": "MIT",
"dependencies": {
"@psf/bch-js": "6.5.3",
"@psf/bch-js": "6.6.0",
"axios": "0.27.2",
"bcryptjs": "2.4.3",
"glob": "7.1.6",
@@ -1481,14 +1481,15 @@
"license": "BSD-3-Clause"
},
"node_modules/@psf/bch-js": {
"version": "6.5.3",
"resolved": "https://registry.npmjs.org/@psf/bch-js/-/bch-js-6.5.3.tgz",
"integrity": "sha512-38UB7NRrVzslZydWZWnB8FVWr5UpxG0cs0Xb1zmovfyOnp96XrAXfWLEuNKstAkpLApegeMY055b7vaKop/wRQ==",
"version": "6.6.0",
"resolved": "http://94.130.170.209:4873/@psf%2fbch-js/-/bch-js-6.6.0.tgz",
"integrity": "sha512-623uPhQQRgf5RusRJVReAxwtSPyMygJKe/qnMBVs8ZWvjFJSr1F+mfS/U8n1dHPV/CHsrcaLUVeSy5ZchNHB6g==",
"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",
@@ -23175,14 +23176,14 @@
"integrity": "sha1-p3c2C1s5oaLlEG+OhY8v0tBgxXA="
},
"@psf/bch-js": {
"version": "6.5.3",
"resolved": "https://registry.npmjs.org/@psf/bch-js/-/bch-js-6.5.3.tgz",
"integrity": "sha512-38UB7NRrVzslZydWZWnB8FVWr5UpxG0cs0Xb1zmovfyOnp96XrAXfWLEuNKstAkpLApegeMY055b7vaKop/wRQ==",
"version": "6.6.0",
"resolved": "http://94.130.170.209:4873/@psf%2fbch-js/-/bch-js-6.6.0.tgz",
"integrity": "sha512-623uPhQQRgf5RusRJVReAxwtSPyMygJKe/qnMBVs8ZWvjFJSr1F+mfS/U8n1dHPV/CHsrcaLUVeSy5ZchNHB6g==",
"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",
@@ -23229,9 +23230,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",
+1 -1
View File
@@ -23,7 +23,7 @@
},
"repository": "Permissionless-Software-Foundation/ipfs-bch-wallet-consumer",
"dependencies": {
"@psf/bch-js": "6.5.3",
"@psf/bch-js": "6.6.0",
"axios": "0.27.2",
"bcryptjs": "2.4.3",
"glob": "7.1.6",
+57 -2
View File
@@ -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
@@ -473,7 +527,7 @@ class BchAdapter {
}
// Get token icon and other media associated with the token.
async getTokenData2 (tokenId) {
async getTokenData2 (tokenId, updateCache) {
try {
// Throw an error if this IPFS node has not yet made a connection to a
// wallet service provider.
@@ -485,7 +539,8 @@ class BchAdapter {
const rpcData = {
endpoint: 'getTokenData2',
tokenId
tokenId,
updateCache
}
// Generate a UUID for the call.
+100 -1
View File
@@ -234,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
@@ -613,8 +710,10 @@ class BchRESTControllerLib {
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)
const data = await this.adapters.bch.getTokenData2(tokenId, updateCache)
console.log(`getTokenData2 data: ${JSON.stringify(data, null, 2)}`)
ctx.body = data
+5
View File
@@ -55,6 +55,7 @@ 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)
@@ -84,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)
}
-3
View File
@@ -1,3 +0,0 @@
/key/swarm/psk/1.0.0/
/base16/
bbd935b70105b03ebd0c6a3c2d2730cd22fc0d18c490ccf689b6c8a22e1bed2a
+76
View File
@@ -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
@@ -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 {
+4
View File
@@ -93,6 +93,10 @@ class BchUseCaseMock {
return {}
}
async getUtxosBulk () {
return {}
}
async broadcast () {
return {}
}