From bf32bf2610e74e504bd2ffa7ef50c2746e4b9758 Mon Sep 17 00:00:00 2001 From: Daniele Ricci Date: Tue, 12 Nov 2024 15:08:26 +0100 Subject: [PATCH] feat: add GET_ADA_HANDLE command --- .vscode/settings.json | 1 + README.md | 27 +++++++++ src/methods/get-ada-handle.ts | 36 ++++++++++++ src/server.ts | 8 +++ src/types/message.ts | 6 ++ src/types/response.ts | 2 + src/utils/message.ts | 8 ++- .../fixtures/preprod/get-ada-handle.e2e.ts | 14 +++++ test/e2e/tests/get-ada-handle.e2e.test.ts | 15 +++++ test/unit/fixtures/getAdaHandle.ts | 57 +++++++++++++++++++ .../unit/tests/methods/get-ada-handle.test.ts | 26 +++++++++ 11 files changed, 199 insertions(+), 1 deletion(-) create mode 100644 src/methods/get-ada-handle.ts create mode 100644 test/e2e/fixtures/preprod/get-ada-handle.e2e.ts create mode 100644 test/e2e/tests/get-ada-handle.e2e.test.ts create mode 100644 test/unit/fixtures/getAdaHandle.ts create mode 100644 test/unit/tests/methods/get-ada-handle.test.ts diff --git a/.vscode/settings.json b/.vscode/settings.json index 376f7fe1..ca15cfff 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -18,6 +18,7 @@ "lovelaces", "memoizee", "nixify", + "preprod", "tailwindcss", "timelock", "txid", diff --git a/README.md b/README.md index 232cae0f..a3808a56 100644 --- a/README.md +++ b/README.md @@ -156,6 +156,7 @@ For each of these commands the client receives an immediate response - [`ESTIMATE_FEE`](#estimate_fee) - estimate transaction submission fee - [`GET_ACCOUNT_INFO`](#get_account_info) - get general information about an account - [`GET_ACCOUNT_UTXO`](#get_account_utxo) - get unspent UTxOs of an account +- [`GET_ADA_HANDLE`](#get_ada_handle) - resolves an Ada Handle - [`GET_BALANCE_HISTORY`](#get_balance_history) - get balance history of an account - [`GET_BLOCK`](#get_block) - get details of a block - [`GET_TRANSACTION`](#get_transaction) - get details of a transaction @@ -468,6 +469,32 @@ Response: } ``` +### GET_ADA_HANDLE + +Resolves an Ada Handle providing the address holding it. + +Input message: + +```ts +{ + "id": number | string; + "command": "GET_ADA_HANDLE"; + "params": { + "name": string; // Ada Handle name + } +} +``` + +Response output message `data` type: + +```ts +{ + id: number | string; + type: 'message'; + data: string | null; +} +``` + ### GET_BLOCK Retrieves detailed information about a specific block using its hash or height. diff --git a/src/methods/get-ada-handle.ts b/src/methods/get-ada-handle.ts new file mode 100644 index 00000000..8a911d03 --- /dev/null +++ b/src/methods/get-ada-handle.ts @@ -0,0 +1,36 @@ +import { prepareMessage } from '../utils/message.js'; +import { blockfrostAPI } from '../utils/blockfrost-api.js'; +import { MessageId } from '../types/message.js'; +import { BlockfrostServerError } from '@blockfrost/blockfrost-js'; + +const policyID = 'f0ff48bbb7bbe9d59a40f1ce90e9e9d0ff5002ec48f232b49ca0fb9a'; + +export default async (id: MessageId, clientId: string, name: string): Promise => { + let data: { address: string } | null; + + try { + const result = await blockfrostAPI.assetsAddresses( + policyID + Buffer.from(name, 'utf8').toString('hex'), + ); + + if (result.length > 1) { + throw new Error('Double minted Ada Handle detected'); + } + + if (result.length === 0) { + data = null; + } else { + const { address } = result[0]; + + data = { address }; + } + } catch (error) { + if (error instanceof BlockfrostServerError && error.status_code === 404) { + data = null; + } else { + throw error; + } + } + + return prepareMessage({ id, clientId, data }); +}; diff --git a/src/server.ts b/src/server.ts index bb363241..bc247ca6 100644 --- a/src/server.ts +++ b/src/server.ts @@ -23,6 +23,7 @@ import { SubscribedAddress, events, onBlock, startEmitter } from './events.js'; import getServerInfo from './methods/get-server-info.js'; import getAccountInfo from './methods/get-account-info.js'; import getAccountUtxo from './methods/get-account-utxo.js'; +import getAdaHandle from './methods/get-ada-handle.js'; import getBlock from './methods/get-block.js'; import getTransaction from './methods/get-transaction.js'; import submitTransaction from './methods/push-transaction.js'; @@ -246,6 +247,13 @@ wss.on('connection', async (ws: Server.Ws) => { break; } + case 'GET_ADA_HANDLE': { + validators.GET_ADA_HANDLE(params); + response = await getAdaHandle(id, clientId, params.name); + + break; + } + case 'GET_BALANCE_HISTORY': { validators.GET_BALANCE_HISTORY(params); response = await getBalanceHistory( diff --git a/src/types/message.ts b/src/types/message.ts index 1d04412b..0a025c58 100644 --- a/src/types/message.ts +++ b/src/types/message.ts @@ -16,6 +16,12 @@ export type Messages = BaseMessage & pageSize?: number; }; } + | { + command: 'GET_ADA_HANDLE'; + params: { + name: string; + }; + } | { command: 'GET_BALANCE_HISTORY'; params: { diff --git a/src/types/response.ts b/src/types/response.ts index fd6d8a9b..58f7d68f 100644 --- a/src/types/response.ts +++ b/src/types/response.ts @@ -91,6 +91,8 @@ export type Responses = { | ServerInfo | AccountInfo | string + | { address: string } + | null | Schemas['block_content'] | BalanceHistoryData[] | TxIdsToTransactionsResponse[] diff --git a/src/utils/message.ts b/src/utils/message.ts index fe024d58..9b414f67 100644 --- a/src/utils/message.ts +++ b/src/utils/message.ts @@ -28,6 +28,12 @@ const schemas: { [k in Validators]: { properties: unknown; required?: string[] } }, required: ['descriptor'], }, + GET_ADA_HANDLE: { + properties: { + name: { type: 'string' }, + }, + required: ['name'], + }, GET_BALANCE_HISTORY: { properties: { descriptor: { type: 'string' }, @@ -35,7 +41,7 @@ const schemas: { [k in Validators]: { properties: unknown; required?: string[] } from: { type: ['number', 'string', 'null'] }, to: { type: ['number', 'string', 'null'] }, }, - required: ['descriptor'], + required: ['descriptor', 'groupBy'], }, GET_BLOCK: { properties: { diff --git a/test/e2e/fixtures/preprod/get-ada-handle.e2e.ts b/test/e2e/fixtures/preprod/get-ada-handle.e2e.ts new file mode 100644 index 00000000..324f22fd --- /dev/null +++ b/test/e2e/fixtures/preprod/get-ada-handle.e2e.ts @@ -0,0 +1,14 @@ +import { expect } from 'vitest'; + +export default [ + { + handle: 'test', + testName: 'GET_ADA_HANDLE success - preprod', + result: { address: expect.any(String) }, + }, + { + handle: 'does_not_exist', + testName: 'GET_ADA_HANDLE not existing - preprod', + result: null, + }, +] as const; diff --git a/test/e2e/tests/get-ada-handle.e2e.test.ts b/test/e2e/tests/get-ada-handle.e2e.test.ts new file mode 100644 index 00000000..0c550cd7 --- /dev/null +++ b/test/e2e/tests/get-ada-handle.e2e.test.ts @@ -0,0 +1,15 @@ +import { describe, expect, test } from 'vitest'; +import { getFixtures } from '../utils/fixtures-loader.js'; +import { getWebSocketClient } from '../utils/setup-websocket-client.js'; + +const fixtures = await getFixtures('get-ada-handle'); + +describe('get-ada-handle', () => { + for (const { handle, result, testName } of fixtures) { + test(testName, async () => { + const ws = getWebSocketClient(); + const { data } = await ws.sendAndWait('GET_ADA_HANDLE', { name: handle }); + expect(data).toMatchObject(result); + }); + } +}); diff --git a/test/unit/fixtures/getAdaHandle.ts b/test/unit/fixtures/getAdaHandle.ts new file mode 100644 index 00000000..886c53a8 --- /dev/null +++ b/test/unit/fixtures/getAdaHandle.ts @@ -0,0 +1,57 @@ +import { BlockfrostServerError } from "@blockfrost/blockfrost-js"; + +export default [ + { + testName: 'getAdaHandle success', + id: 1, + assets: [{ + address: 'address', + quantity: '1', + }], + result: { + id: 1, + type: 'message', + data: { address: 'address' }, + }, + }, + { + testName: 'getAdaHandle not found', + id: 1, + error: new BlockfrostServerError({ + error: 'error', + message: 'Not found', + status_code: 404, + url: 'url', + }), + result: { + id: 1, + type: 'message', + data: null, + }, + }, + { + testName: 'getAdaHandle empty result', + id: 1, + assets: [], + result: { + id: 1, + type: 'message', + data: null, + }, + }, + { + testName: 'getAdaHandle double minted', + id: 1, + assets: [ + { + address: 'address1', + quantity: '1', + }, + { + address: 'address2', + quantity: '1', + } + ], + thrown: new Error('Double minted Ada Handle detected'), + }, +]; diff --git a/test/unit/tests/methods/get-ada-handle.test.ts b/test/unit/tests/methods/get-ada-handle.test.ts new file mode 100644 index 00000000..5d31a8b5 --- /dev/null +++ b/test/unit/tests/methods/get-ada-handle.test.ts @@ -0,0 +1,26 @@ +import sinon from 'sinon'; +import { describe, test, expect } from 'vitest'; +import fixtures from '../../fixtures/getAdaHandle.js'; +import { blockfrostAPI } from '../../../../src/utils/blockfrost-api.js'; +import getAdaHandle from '../../../../src/methods/get-ada-handle.js'; + +describe('getAdaHandle', () => { + for (const fixture of fixtures) { + test(fixture.testName, async () => { + const mock1 = fixture.assets ? + sinon.stub(blockfrostAPI, 'assetsAddresses').resolves(fixture.assets) : + sinon.stub(blockfrostAPI, 'assetsAddresses').rejects(fixture.error); + + if(fixture.result) { + const result = await getAdaHandle(1, 'test', 'test'); + + expect(result).toBe(JSON.stringify(fixture.result)); + } + else { + await expect(getAdaHandle(1, 'test', 'test')).rejects.toEqual(fixture.thrown); + } + + mock1.restore(); + }); + } +});