diff --git a/server/package-lock.json b/server/package-lock.json index 96a50b2..48c747f 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -9,7 +9,7 @@ "version": "0.1.0-alpha.1", "license": "ISC", "dependencies": { - "@hyperledger/firefly-sdk": "^0.1.0-alpha.10", + "@hyperledger/firefly-sdk": "^0.1.0-alpha.12", "body-parser": "^1.20.0", "class-transformer": "^0.3.1", "class-validator": "^0.12.2", @@ -855,9 +855,9 @@ "dev": true }, "node_modules/@hyperledger/firefly-sdk": { - "version": "0.1.0-alpha.10", - "resolved": "https://registry.npmjs.org/@hyperledger/firefly-sdk/-/firefly-sdk-0.1.0-alpha.10.tgz", - "integrity": "sha512-BNlOgtWFRIWnMpe6qvW16dbEUBI6wijYkUMFaZgLqjmzLT6/juxlQWUaaxJHVXv9CQa14wxxc3tfZYLC9A2mDg==", + "version": "0.1.0-alpha.12", + "resolved": "https://registry.npmjs.org/@hyperledger/firefly-sdk/-/firefly-sdk-0.1.0-alpha.12.tgz", + "integrity": "sha512-C13d3qx28QTlHGTqXXPMcBtxN8VhwrF6yjrzpDrmprC9d2P8kF9wVMe9lJUBV48sTKFOntjSmZl7kzEx72XyAQ==", "dependencies": { "axios": "^0.26.1", "form-data": "^4.0.0", @@ -11355,9 +11355,9 @@ "dev": true }, "@hyperledger/firefly-sdk": { - "version": "0.1.0-alpha.10", - "resolved": "https://registry.npmjs.org/@hyperledger/firefly-sdk/-/firefly-sdk-0.1.0-alpha.10.tgz", - "integrity": "sha512-BNlOgtWFRIWnMpe6qvW16dbEUBI6wijYkUMFaZgLqjmzLT6/juxlQWUaaxJHVXv9CQa14wxxc3tfZYLC9A2mDg==", + "version": "0.1.0-alpha.12", + "resolved": "https://registry.npmjs.org/@hyperledger/firefly-sdk/-/firefly-sdk-0.1.0-alpha.12.tgz", + "integrity": "sha512-C13d3qx28QTlHGTqXXPMcBtxN8VhwrF6yjrzpDrmprC9d2P8kF9wVMe9lJUBV48sTKFOntjSmZl7kzEx72XyAQ==", "requires": { "axios": "^0.26.1", "form-data": "^4.0.0", diff --git a/server/package.json b/server/package.json index 7cc6da0..8e16d63 100644 --- a/server/package.json +++ b/server/package.json @@ -26,7 +26,7 @@ }, "homepage": "https://github.com/hyperledger/firefly-sandbox#readme", "dependencies": { - "@hyperledger/firefly-sdk": "^0.1.0-alpha.10", + "@hyperledger/firefly-sdk": "^0.1.0-alpha.12", "body-parser": "^1.20.0", "class-transformer": "^0.3.1", "class-validator": "^0.12.2", diff --git a/server/src/controllers/tokens.ts b/server/src/controllers/tokens.ts index bf5b046..067ab41 100644 --- a/server/src/controllers/tokens.ts +++ b/server/src/controllers/tokens.ts @@ -7,9 +7,12 @@ import { QueryParam, Req, UploadedFile, + Param, } from 'routing-controllers'; import { Request } from 'express'; import { OpenAPI, ResponseSchema } from 'routing-controllers-openapi'; +import { plainToClassFromExist } from 'class-transformer'; +import { FireFlyTokenPoolResponse } from '@hyperledger/firefly-sdk'; import { firefly } from '../clients/firefly'; import { formatTemplate, @@ -28,7 +31,6 @@ import { MintBurnBlob, TransferBlob, } from '../interfaces'; -import { plainToClassFromExist } from 'class-transformer'; /** * Tokens - API Server @@ -41,13 +43,15 @@ export class TokensController { @OpenAPI({ summary: 'List all token pools' }) async tokenpools(): Promise { const pools = await firefly.getTokenPools(); - return pools.map((p) => ({ - id: p.id, - name: p.name, - symbol: p.symbol, - type: p.type, - decimals: p.decimals, - })); + return pools.map((p) => mapPool(p)); + } + + @Get('/pools/:id') + @ResponseSchema(TokenPool) + @OpenAPI({ summary: 'Look up a token pool by ID' }) + async tokenpool(@Param('id') id: string): Promise { + const pool = await firefly.getTokenPool(id); + return mapPool(pool); } @Post('/pools') @@ -224,7 +228,7 @@ export class TokensController { for (const b of balances) { if (!poolMap.has(b.pool)) { const pool = await firefly.getTokenPool(b.pool); - poolMap.set(b.pool, pool); + poolMap.set(b.pool, mapPool(pool)); } } return balances.map((b) => ({ @@ -236,6 +240,20 @@ export class TokensController { } } +function mapPool(pool: FireFlyTokenPoolResponse) { + // Some contracts (base ERC20/ERC721) do not support passing extra data. + // The UI needs to adjust for this, as some items won't reliably confirm. + const schema: string = pool.info?.schema ?? ''; + return { + id: pool.id, + name: pool.name, + symbol: pool.symbol, + type: pool.type, + decimals: pool.decimals ?? 0, + dataSupport: schema.indexOf('NoData') === -1, + }; +} + /** * Tokens - Code Templates * Allows the frontend to display representative code snippets for backend operations. diff --git a/server/src/controllers/websocket.ts b/server/src/controllers/websocket.ts index 78fd990..e873bac 100644 --- a/server/src/controllers/websocket.ts +++ b/server/src/controllers/websocket.ts @@ -3,6 +3,7 @@ import { nanoid } from 'nanoid'; import { firefly } from '../clients/firefly'; import { WebsocketHandler } from '../utils'; import Logger from '../logger'; +import { FF_EVENTS, FF_TX } from '../enums'; /** * Simple WebSocket Server @@ -24,10 +25,30 @@ export class SimpleWebSocket { }; const ffSocket = firefly.listen(sub, async (socket, event) => { - if (event.type === 'transaction_submitted' && event.transaction?.type === 'batch_pin') { - // Enrich batch_pin transaction events with details on the batch - const batches = await firefly.getBatches({ 'tx.id': event.tx }); - event['batch'] = batches[0]; + if (event.type === FF_EVENTS.TX_SUBMITTED) { + if (event.transaction?.type === FF_TX.BATCH_PIN) { + // Enrich batch_pin transaction events with details on the batch + const batches = await firefly.getBatches({ 'tx.id': event.tx }); + event['batch'] = batches[0]; + } else if (event.transaction?.type === FF_TX.TOKEN_TRANSFER) { + // Enrich token_transfer transaction events with pool ID + const operations = await firefly.getOperations({ + tx: event.tx, + type: FF_TX.TOKEN_TRANSFER, + }); + if (operations.length > 0) { + event['pool'] = operations[0].input?.pool; + } + } else if (event.transaction?.type === FF_TX.TOKEN_APPROVAL) { + // Enrich token_approval transaction events with pool ID + const operations = await firefly.getOperations({ + tx: event.tx, + type: FF_TX.TOKEN_APPROVAL, + }); + if (operations.length > 0) { + event['pool'] = operations[0].input?.pool; + } + } } // Forward the event to the client diff --git a/server/src/enums.ts b/server/src/enums.ts new file mode 100644 index 0000000..0d37506 --- /dev/null +++ b/server/src/enums.ts @@ -0,0 +1,36 @@ +export enum FF_EVENTS { + // Blockchain Event + BLOCKCHAIN_EVENT_RECEIVED = 'blockchain_event_received', + BLOCKCHAIN_INVOKE_OP_SUCCEEDED = 'blockchain_invoke_op_succeeded', + BLOCKCHAIN_INVOKE_OP_FAILED = 'blockchain_invoke_op_failed', + CONTRACT_API_CONFIRMED = 'contract_api_confirmed', + CONTRACT_INTERFACE_CONFIRMED = 'contract_interface_confirmed', + DATATYPE_CONFIRMED = 'datatype_confirmed', + IDENTITY_CONFIRMED = 'identity_confirmed', + IDENTITY_UPDATED = 'identity_updated', + NS_CONFIRMED = 'namespace_confirmed', + // Message/Definitions + MSG_CONFIRMED = 'message_confirmed', + MSG_REJECTED = 'message_rejected', + TX_SUBMITTED = 'transaction_submitted', + // Transfers + TOKEN_POOL_CONFIRMED = 'token_pool_confirmed', + TOKEN_POOL_OP_FAILED = 'token_pool_op_failed', + TOKEN_APPROVAL_CONFIRMED = 'token_approval_confirmed', + TOKEN_APPROVAL_OP_FAILED = 'token_approval_op_failed', + TOKEN_TRANSFER_CONFIRMED = 'token_transfer_confirmed', + TOKEN_TRANSFER_OP_FAILED = 'token_transfer_op_failed', +} + +export enum FF_TX { + NONE = 'none', + // Blockchain Event + CONTRACT_INVOKE = 'contract_invoke', + //Message/Definitions + BATCH_PIN = 'batch_pin', + UNPINNED = 'unpinned', + // Transfers + TOKEN_APPROVAL = 'token_approval', + TOKEN_POOL = 'token_pool', + TOKEN_TRANSFER = 'token_transfer', +} diff --git a/server/src/interfaces.ts b/server/src/interfaces.ts index 3ce893f..64e5045 100644 --- a/server/src/interfaces.ts +++ b/server/src/interfaces.ts @@ -1,8 +1,10 @@ import { ArrayNotEmpty, + IsBoolean, IsDefined, IsEnum, IsInstance, + IsInt, IsNumberString, IsObject, IsOptional, @@ -146,6 +148,12 @@ export class TokenPoolInput { export class TokenPool extends TokenPoolInput { @IsUUID() id: string; + + @IsInt() + decimals: number; + + @IsBoolean() + dataSupport: boolean; } export class TokenMintBurn { diff --git a/server/test/tokens.test.ts b/server/test/tokens.test.ts index b8fd3ca..695d8a2 100644 --- a/server/test/tokens.test.ts +++ b/server/test/tokens.test.ts @@ -24,7 +24,10 @@ describe('Tokens', () => { await request(server) .get('/api/tokens/pools') .expect(200) - .expect([{ id: 'pool1' }, { id: 'pool2' }]); + .expect([ + { id: 'pool1', decimals: 0, dataSupport: true }, + { id: 'pool2', decimals: 0, dataSupport: true }, + ]); expect(mockFireFly.getTokenPools).toHaveBeenCalledWith(); }); @@ -144,7 +147,19 @@ describe('Tokens', () => { await request(server) .get('/api/tokens/balances?pool=poolA&key=0x123') .expect(200) - .expect([{ key: '0x123', balance: '1', pool: pool }]); + .expect([ + { + key: '0x123', + balance: '1', + pool: { + name: 'poolA', + type: 'fungible', + id: 'poolA', + decimals: 0, + dataSupport: true, + }, + }, + ]); expect(mockFireFly.getTokenBalances).toHaveBeenCalledWith({ pool: 'poolA',