From c2ec08f03d4496043bd3ff9df1cf2994755716ed Mon Sep 17 00:00:00 2001 From: Chris Troutner Date: Tue, 10 May 2022 13:18:24 -0700 Subject: [PATCH] feat(getTokenData): Added endpoint for getting data associated with a token --- src/adapters/bch/index.js | 42 +++++++++++ src/controllers/rest-api/bch/controller.js | 69 ++++++++++++++++++- src/controllers/rest-api/bch/index.js | 6 +- test/unit/adapters/bch.adapter.unit.js | 30 ++++++++ .../rest-api/bch/bch.rest.controller.unit.js | 36 ++++++++++ 5 files changed, 181 insertions(+), 2 deletions(-) diff --git a/src/adapters/bch/index.js b/src/adapters/bch/index.js index 83f7bce..b64319f 100644 --- a/src/adapters/bch/index.js +++ b/src/adapters/bch/index.js @@ -429,6 +429,48 @@ class BchAdapter { } } + 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 { diff --git a/src/controllers/rest-api/bch/controller.js b/src/controllers/rest-api/bch/controller.js index 27d1e55..50e7ef2 100644 --- a/src/controllers/rest-api/bch/controller.js +++ b/src/controllers/rest-api/bch/controller.js @@ -547,7 +547,74 @@ class BchRESTControllerLib { const utxo = ctx.request.body.utxo const data = await this.adapters.bch.utxoIsValid(utxo) - console.log(`utxoIsValid data: ${JSON.stringify(data, null, 2)}`) + // 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) { diff --git a/src/controllers/rest-api/bch/index.js b/src/controllers/rest-api/bch/index.js index 8afbf29..245462b 100644 --- a/src/controllers/rest-api/bch/index.js +++ b/src/controllers/rest-api/bch/index.js @@ -60,7 +60,7 @@ class BchRouter { 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('/getTokenData', this.getTokenData) // Attach the Controller routes to the Koa app. app.use(this.router.routes()) @@ -102,6 +102,10 @@ class BchRouter { async utxoIsValid (ctx, next) { await _this.bchRESTController.utxoIsValid(ctx, next) } + + async getTokenData (ctx, next) { + await _this.bchRESTController.getTokenData(ctx, next) + } } module.exports = BchRouter diff --git a/test/unit/adapters/bch.adapter.unit.js b/test/unit/adapters/bch.adapter.unit.js index 764fdeb..2ea2f86 100644 --- a/test/unit/adapters/bch.adapter.unit.js +++ b/test/unit/adapters/bch.adapter.unit.js @@ -314,4 +314,34 @@ describe('#bch-use-case', () => { } }) }) + + 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') + } + }) + }) }) diff --git a/test/unit/controllers/rest-api/bch/bch.rest.controller.unit.js b/test/unit/controllers/rest-api/bch/bch.rest.controller.unit.js index 0ef9bf9..e379417 100644 --- a/test/unit/controllers/rest-api/bch/bch.rest.controller.unit.js +++ b/test/unit/controllers/rest-api/bch/bch.rest.controller.unit.js @@ -358,4 +358,40 @@ describe('#BCH-REST-Controller', () => { 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) + }) + }) })