diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2b989b9..27f68d0 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -3,13 +3,12 @@ on: [push] jobs: test: runs-on: ubuntu-latest - services: steps: - name: Checkout code uses: actions/checkout@v4 - name: Install Nix - uses: cachix/install-nix-action/ba0dd844c9180cbf77aa72a116d6fbc515d0e87b + uses: cachix/install-nix-action@ba0dd844c9180cbf77aa72a116d6fbc515d0e87b with: nix_path: nixpkgs=channel:nixos-unstable extra_nix_config: | @@ -24,8 +23,8 @@ jobs: - name: lint run: | - nix develop . -c yarn workspace hathor-rpc-handler run lint + nix develop . -c yarn workspace @hathor/hathor-rpc-handler run lint - name: tests run: | - nix develop . -c yarn workspace hathor-rpc-handler run test + nix develop . -c yarn workspace @hathor/hathor-rpc-handler run test diff --git a/packages/hathor-rpc-handler/__tests__/mocks/index.ts b/packages/hathor-rpc-handler/__tests__/mocks/index.ts index 00a5a69..59a038c 100644 --- a/packages/hathor-rpc-handler/__tests__/mocks/index.ts +++ b/packages/hathor-rpc-handler/__tests__/mocks/index.ts @@ -5,12 +5,11 @@ import { GetConnectedNetworkRpcRequest, GetUtxosRpcRequest, RpcMethods, + SignOracleDataRpcRequest, SignWithAddressRpcRequest, } from '../../src/types'; export const mockGetBalanceRequest: GetBalanceRpcRequest = { - id: '1', - jsonrpc: '2.0', method: RpcMethods.GetBalance, params: { network: 'mainnet', @@ -19,8 +18,6 @@ export const mockGetBalanceRequest: GetBalanceRpcRequest = { }; export const mockGetAddressRequest: GetAddressRpcRequest = { - id: '1', - jsonrpc: '2.0', method: RpcMethods.GetAddress, params: { network: 'mainnet', @@ -30,8 +27,6 @@ export const mockGetAddressRequest: GetAddressRpcRequest = { }; export const mockGetUtxosRequest: GetUtxosRpcRequest = { - id: '1', - jsonrpc: '2.0', method: RpcMethods.GetUtxos, params: { network: 'mainnet', @@ -46,8 +41,6 @@ export const mockGetUtxosRequest: GetUtxosRpcRequest = { }; export const mockSignWithAddressRequest: SignWithAddressRpcRequest = { - id: '1', - jsonrpc: '2.0', method: RpcMethods.SignWithAddress, params: { network: 'mainnet', @@ -56,9 +49,16 @@ export const mockSignWithAddressRequest: SignWithAddressRpcRequest = { }, }; +export const mockSignOracleDataRequest: SignOracleDataRpcRequest = { + method: RpcMethods.SignOracleData, + params: { + network: 'mainnet', + oracle: 'address1', + data: 'Test oracle data', + }, +}; + export const mockGetConnectedNetworkRequest: GetConnectedNetworkRpcRequest = { - id: '1', - jsonrpc: '2.0', method: RpcMethods.GetConnectedNetwork, }; diff --git a/packages/hathor-rpc-handler/__tests__/rpcMethods/createToken.test.ts b/packages/hathor-rpc-handler/__tests__/rpcMethods/createToken.test.ts index 24ceeca..16c0fc9 100644 --- a/packages/hathor-rpc-handler/__tests__/rpcMethods/createToken.test.ts +++ b/packages/hathor-rpc-handler/__tests__/rpcMethods/createToken.test.ts @@ -9,6 +9,23 @@ import { } from '../../src/types'; import { CreateTokenError, PromptRejectedError } from '../../src/errors'; +function toCamelCase(params: Pick['params']) { + return { + name: params.name, + symbol: params.symbol, + changeAddress: params.change_address, + address: params.address, + amount: params.amount, + createMint: params.create_mint, + mintAuthorityAddress: params.mint_authority_address, + allowExternalMintAuthorityAddress: params.allow_external_mint_authority_address, + createMelt: params.create_melt, + meltAuthorityAddress: params.melt_authority_address, + allowExternalMeltAuthorityAddress: params.allow_external_melt_authority_address, + data: params.data, + }; +} + describe('createToken', () => { let rpcRequest: CreateTokenRpcRequest; let wallet: HathorWallet; @@ -17,22 +34,20 @@ describe('createToken', () => { beforeEach(() => { rpcRequest = { method: RpcMethods.CreateToken, - id: '1', - jsonrpc: '2.0', params: { - name: 'myToken', + name: 'mytoken', symbol: 'mtk', amount: 1000, address: 'address123', - changeAddress: 'changeAddress123', - createMint: true, - mintAuthorityAddress: null, - allowExternalMintAuthorityAddress: false, - createMelt: true, - meltAuthorityAddress: null, - allowExternalMeltAuthorityAddress: false, + change_address: 'changeAddress123', + create_mint: true, + mint_authority_address: null, + allow_external_mint_authority_address: false, + create_melt: true, + melt_authority_address: null, + allow_external_melt_authority_address: false, data: null, - pushTx: true, + push_tx: true, network: 'mainnet', }, } as unknown as CreateTokenRpcRequest; @@ -73,12 +88,12 @@ describe('createToken', () => { const result = await createToken(rpcRequest, wallet, {}, triggerHandler); - expect(triggerHandler).toHaveBeenCalledTimes(2); + expect(triggerHandler).toHaveBeenCalledTimes(4); expect(triggerHandler).toHaveBeenCalledWith( { type: TriggerTypes.CreateTokenConfirmationPrompt, method: rpcRequest.method, - data: rpcRequest.params, + data: toCamelCase(rpcRequest.params), }, {} ); @@ -95,15 +110,10 @@ describe('createToken', () => { rpcRequest.params.symbol, rpcRequest.params.amount, { - changeAddress: rpcRequest.params.change_address, - address: rpcRequest.params.address, - createMint: rpcRequest.params.create_mint, - mintAuthorityAddress: rpcRequest.params.mint_authority_address, - allowExternalMintAuthorityAddress: rpcRequest.params.allow_external_mint_authority_address, - createMelt: rpcRequest.params.create_melt, - meltAuthorityAddress: rpcRequest.params.melt_authority_address, - allowExternalMeltAuthorityAddress: rpcRequest.params.allow_external_melt_authority_address, - data: rpcRequest.params.data, + ...toCamelCase(rpcRequest.params), + amount: undefined, + name: undefined, + symbol: undefined, pinCode, } ); @@ -127,7 +137,7 @@ describe('createToken', () => { { type: TriggerTypes.CreateTokenConfirmationPrompt, method: rpcRequest.method, - data: rpcRequest.params, + data: toCamelCase(rpcRequest.params), }, {} ); @@ -156,7 +166,7 @@ describe('createToken', () => { await expect(createToken(rpcRequest, wallet, {}, triggerHandler)).rejects.toThrow(CreateTokenError); - expect(triggerHandler).toHaveBeenCalledTimes(2); + expect(triggerHandler).toHaveBeenCalledTimes(3); }); it('should throw an error if the change address is not owned by the wallet', async () => { diff --git a/packages/hathor-rpc-handler/__tests__/rpcMethods/getAddress.test.ts b/packages/hathor-rpc-handler/__tests__/rpcMethods/getAddress.test.ts index e1ea55d..4688ad4 100644 --- a/packages/hathor-rpc-handler/__tests__/rpcMethods/getAddress.test.ts +++ b/packages/hathor-rpc-handler/__tests__/rpcMethods/getAddress.test.ts @@ -31,8 +31,6 @@ describe('getAddress', () => { it('should return the current address for type "first_empty"', async () => { const rpcRequest: GetAddressRpcRequest = { - id: '3', - jsonrpc: '2.0', params: { type: 'first_empty', network: 'mainnet' }, method: RpcMethods.GetAddress, }; @@ -47,8 +45,6 @@ describe('getAddress', () => { it('should throw NotImplementedError for type "full_path"', async () => { const rpcRequest: GetAddressRpcRequest = { - id: '3', - jsonrpc: '2.0', params: { type: 'full_path', network: 'mainnet' }, method: RpcMethods.GetAddress, }; @@ -58,8 +54,6 @@ describe('getAddress', () => { it('should return the address at index for type "index"', async () => { const rpcRequest: GetAddressRpcRequest = { - id: '3', - jsonrpc: '2.0', params: { type: 'index', index: 5, network: 'mainnet' }, method: RpcMethods.GetAddress, }; @@ -74,8 +68,6 @@ describe('getAddress', () => { it('should return the client address for type "client"', async () => { const rpcRequest: GetAddressRpcRequest = { - id: '3', - jsonrpc: '2.0', params: { type: 'client', network: 'mainnet' }, method: RpcMethods.GetAddress, }; @@ -93,8 +85,6 @@ describe('getAddress', () => { it('should throw PromptRejectedError if address confirmation is rejected', async () => { const rpcRequest: GetAddressRpcRequest = { - id: '3', - jsonrpc: '2.0', params: { type: 'first_empty', network: 'mainnet' }, method: RpcMethods.GetAddress, }; @@ -106,8 +96,6 @@ describe('getAddress', () => { it('should confirm the address if type is not "client"', async () => { const rpcRequest: GetAddressRpcRequest = { - id: '3', - jsonrpc: '2.0', params: { type: 'first_empty', network: 'mainnet' }, method: RpcMethods.GetAddress, }; diff --git a/packages/hathor-rpc-handler/__tests__/rpcMethods/sendNanoContractTx.test.ts b/packages/hathor-rpc-handler/__tests__/rpcMethods/sendNanoContractTx.test.ts index d0b5bcd..24729a5 100644 --- a/packages/hathor-rpc-handler/__tests__/rpcMethods/sendNanoContractTx.test.ts +++ b/packages/hathor-rpc-handler/__tests__/rpcMethods/sendNanoContractTx.test.ts @@ -80,7 +80,7 @@ describe('sendNanoContractTx', () => { const result = await sendNanoContractTx(rpcRequest, wallet, {}, promptHandler); - expect(promptHandler).toHaveBeenCalledTimes(3); + expect(promptHandler).toHaveBeenCalledTimes(4); expect(promptHandler).toHaveBeenCalledWith({ type: TriggerTypes.PinConfirmationPrompt, method: rpcRequest.method, diff --git a/packages/hathor-rpc-handler/__tests__/rpcMethods/signOracleData.test.ts b/packages/hathor-rpc-handler/__tests__/rpcMethods/signOracleData.test.ts new file mode 100644 index 0000000..53a992c --- /dev/null +++ b/packages/hathor-rpc-handler/__tests__/rpcMethods/signOracleData.test.ts @@ -0,0 +1,130 @@ +/** + * Copyright (c) Hathor Labs and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { HathorWallet, Network, bufferUtils, nanoUtils } from '@hathor/wallet-lib'; +import { TriggerTypes, TriggerResponseTypes, RpcResponseTypes } from '../../src/types'; +import { mockPromptHandler, mockSignOracleDataRequest } from '../mocks'; +import { signOracleData } from '../../src/rpcMethods/signOracleData'; +import { PromptRejectedError } from '../../src/errors'; + +jest.mock('@hathor/wallet-lib', () => ({ + ...jest.requireActual('@hathor/wallet-lib'), + nanoUtils: { + getOracleBuffer: jest.fn().mockReturnValue(Buffer.from('oracle-data')), + getOracleInputData: jest.fn().mockResolvedValue(Buffer.from('oracle-data')), + }, + NanoContractSerializer: jest.fn().mockImplementation(() => ({ + serializeFromType: jest.fn(), + })), +})); + +describe('signOracleData', () => { + let wallet: jest.Mocked; + + afterEach(() => { + jest.clearAllMocks(); + }); + + beforeEach(() => { + wallet = { + getNetwork: jest.fn().mockReturnValue('mainnet'), + getNetworkObject: jest.fn().mockReturnValue(new Network('mainnet')), + } as unknown as HathorWallet; + }); + + it('should throw PromptRejectedError if user rejects the sign oracle data trigger request', async () => { + mockPromptHandler.mockResolvedValueOnce(false); + + await expect(signOracleData(mockSignOracleDataRequest, wallet, {}, mockPromptHandler)).rejects.toThrow(PromptRejectedError); + + expect(mockPromptHandler).toHaveBeenNthCalledWith(1, { + type: TriggerTypes.SignOracleDataConfirmationPrompt, + method: mockSignOracleDataRequest.method, + data: { + oracle: mockSignOracleDataRequest.params.oracle, + data: mockSignOracleDataRequest.params.data, + }, + }, {}); + + expect(nanoUtils.getOracleBuffer).not.toHaveBeenCalled(); + }); + + it('should throw PromptRejectedError if user rejects the PIN prompt', async () => { + mockPromptHandler + .mockResolvedValueOnce({ + type: TriggerResponseTypes.SignOracleDataConfirmationResponse, + data: true, + }) + .mockResolvedValueOnce({ + type: TriggerResponseTypes.PinRequestResponse, + data: { + accepted: false + }, + }); + + await expect(signOracleData(mockSignOracleDataRequest, wallet, {}, mockPromptHandler)).rejects.toThrow(PromptRejectedError); + + expect(mockPromptHandler).toHaveBeenNthCalledWith(1, { + type: TriggerTypes.SignOracleDataConfirmationPrompt, + method: mockSignOracleDataRequest.method, + data: { + oracle: mockSignOracleDataRequest.params.oracle, + data: mockSignOracleDataRequest.params.data, + }, + }, {}); + + expect(mockPromptHandler).toHaveBeenNthCalledWith(2, { + type: TriggerTypes.PinConfirmationPrompt, + method: mockSignOracleDataRequest.method, + }, {}); + + expect(nanoUtils.getOracleBuffer).not.toHaveBeenCalled(); + }); + + it('should return signed oracle data if user confirms and provides PIN', async () => { + mockPromptHandler + .mockResolvedValueOnce({ + type: TriggerResponseTypes.SignOracleDataConfirmationResponse, + data: true, + }) + .mockResolvedValueOnce({ + type: TriggerResponseTypes.PinRequestResponse, + data: { + accepted: true, + pinCode: 'mock_pin', + }, + }); + + const result = await signOracleData(mockSignOracleDataRequest, wallet, {}, mockPromptHandler); + + expect(mockPromptHandler).toHaveBeenNthCalledWith(1, { + type: TriggerTypes.SignOracleDataConfirmationPrompt, + method: mockSignOracleDataRequest.method, + data: { + oracle: mockSignOracleDataRequest.params.oracle, + data: mockSignOracleDataRequest.params.data, + }, + }, {}); + + expect(mockPromptHandler).toHaveBeenNthCalledWith(2, { + type: TriggerTypes.PinConfirmationPrompt, + method: mockSignOracleDataRequest.method, + }, {}); + + const oracleDataBuf = Buffer.from('oracle-data'); + const signature = `${bufferUtils.bufferToHex(oracleDataBuf)},${mockSignOracleDataRequest.params.data},str`; + + expect(result).toStrictEqual({ + type: RpcResponseTypes.SignOracleDataResponse, + response: { + data: mockSignOracleDataRequest.params.data, + signature, + oracle: mockSignOracleDataRequest.params.oracle, + } + }); + }); +}); diff --git a/packages/hathor-rpc-handler/eslint.config.js b/packages/hathor-rpc-handler/eslint.config.js index 8754db9..105d9bd 100644 --- a/packages/hathor-rpc-handler/eslint.config.js +++ b/packages/hathor-rpc-handler/eslint.config.js @@ -7,9 +7,6 @@ export default tseslint.config( eslint.configs.recommended, ...tseslint.configs.recommended, { - ignores: [ - 'dist/**/*.js', - ], rules: { '@typescript-eslint/no-unused-vars': ['error', { 'argsIgnorePattern': '^_' }], }, diff --git a/packages/hathor-rpc-handler/package.json b/packages/hathor-rpc-handler/package.json index 99160b5..c0ef8cd 100644 --- a/packages/hathor-rpc-handler/package.json +++ b/packages/hathor-rpc-handler/package.json @@ -1,8 +1,8 @@ { - "name": "hathor-rpc-handler", + "name": "@hathor/hathor-rpc-handler", "license": "MIT", - "main": "dist/index.js", "type": "module", + "main": "dist/index.js", "typings": "dist/index.d.ts", "files": [ "dist", @@ -12,7 +12,7 @@ "node": ">=20" }, "scripts": { - "lint": "eslint .", + "lint": "eslint . --ignore-pattern 'dist/*'", "test": "jest", "build": "tsc --declaration", "watch": "tsc -w" diff --git a/packages/hathor-rpc-handler/src/helpers/index.ts b/packages/hathor-rpc-handler/src/helpers/index.ts index ab2ae83..dec3712 100644 --- a/packages/hathor-rpc-handler/src/helpers/index.ts +++ b/packages/hathor-rpc-handler/src/helpers/index.ts @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ -import { HathorWallet } from '@hathor/wallet-lib'; +import type { HathorWallet } from '@hathor/wallet-lib'; import { DifferentNetworkError } from '../errors'; export function validateNetwork(wallet: HathorWallet, network: string) { diff --git a/packages/hathor-rpc-handler/src/index.ts b/packages/hathor-rpc-handler/src/index.ts index e7f55a2..a710fcc 100644 --- a/packages/hathor-rpc-handler/src/index.ts +++ b/packages/hathor-rpc-handler/src/index.ts @@ -6,6 +6,7 @@ */ export * from './rpcHandler'; +export * from './rpcRequest'; export * from './types'; export * from './rpcMethods'; export * from './errors'; diff --git a/packages/hathor-rpc-handler/src/rpcHandler/index.ts b/packages/hathor-rpc-handler/src/rpcHandler/index.ts index c182663..ec5faa4 100644 --- a/packages/hathor-rpc-handler/src/rpcHandler/index.ts +++ b/packages/hathor-rpc-handler/src/rpcHandler/index.ts @@ -5,6 +5,7 @@ * LICENSE file in the root directory of this source tree. */ +import type { HathorWallet } from '@hathor/wallet-lib'; import { GetAddressRpcRequest, GetBalanceRpcRequest, @@ -18,11 +19,17 @@ import { SignWithAddressRpcRequest, RpcResponse, CreateTokenRpcRequest, + SignOracleDataRpcRequest, } from '../types'; -import { signWithAddress } from '../rpcMethods/signWithAddress'; -import { HathorWallet } from '@hathor/wallet-lib'; -import { getAddress, getBalance, getUtxos, sendNanoContractTx } from '../rpcMethods'; -import { getConnectedNetwork } from '../rpcMethods/getConnectedNetwork'; +import { + getAddress, + getBalance, + getUtxos, + sendNanoContractTx, + getConnectedNetwork, + signOracleData, + signWithAddress, +} from '../rpcMethods'; import { InvalidRpcMethod } from '../errors'; import { createToken } from '../rpcMethods/createToken'; @@ -63,14 +70,20 @@ export const handleRpcRequest = async ( requestMetadata, promptHandler, ); - case RpcMethods.SendNanoContractTx: return sendNanoContractTx( - request as SendNanoContractRpcRequest, + case RpcMethods.CreateToken: return createToken( + request as CreateTokenRpcRequest, wallet, requestMetadata, promptHandler, ); - case RpcMethods.CreateToken: return createToken( - request as CreateTokenRpcRequest, + case RpcMethods.SignOracleData: return signOracleData( + request as SignOracleDataRpcRequest, + wallet, + requestMetadata, + promptHandler, + ); + case RpcMethods.SendNanoContractTx: return sendNanoContractTx( + request as SendNanoContractRpcRequest, wallet, requestMetadata, promptHandler, diff --git a/packages/hathor-rpc-handler/src/rpcMethods/getAddress.ts b/packages/hathor-rpc-handler/src/rpcMethods/getAddress.ts index c3e478e..bdc9ce7 100644 --- a/packages/hathor-rpc-handler/src/rpcMethods/getAddress.ts +++ b/packages/hathor-rpc-handler/src/rpcMethods/getAddress.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { HathorWallet } from '@hathor/wallet-lib'; +import type { HathorWallet } from '@hathor/wallet-lib'; import { AddressRequestClientResponse, TriggerTypes, @@ -17,7 +17,7 @@ import { } from '../types'; import { NotImplementedError, PromptRejectedError } from '../errors'; import { validateNetwork } from '../helpers'; -import { AddressInfoObject } from '@hathor/wallet-lib/lib/wallet/types'; +import type { AddressInfoObject } from '@hathor/wallet-lib/lib/wallet/types'; /** * Gets an address based on the provided rpcRequest and wallet. diff --git a/packages/hathor-rpc-handler/src/rpcMethods/getBalance.ts b/packages/hathor-rpc-handler/src/rpcMethods/getBalance.ts index c036b78..b5c7389 100644 --- a/packages/hathor-rpc-handler/src/rpcMethods/getBalance.ts +++ b/packages/hathor-rpc-handler/src/rpcMethods/getBalance.ts @@ -5,8 +5,8 @@ * LICENSE file in the root directory of this source tree. */ -import { HathorWallet } from '@hathor/wallet-lib'; -import { GetBalanceObject } from '@hathor/wallet-lib/lib/wallet/types'; +import type { HathorWallet } from '@hathor/wallet-lib'; +import type { GetBalanceObject } from '@hathor/wallet-lib/lib/wallet/types'; import { TriggerTypes, GetBalanceConfirmationPrompt, diff --git a/packages/hathor-rpc-handler/src/rpcMethods/getConnectedNetwork.ts b/packages/hathor-rpc-handler/src/rpcMethods/getConnectedNetwork.ts index 1071b18..d6b858f 100644 --- a/packages/hathor-rpc-handler/src/rpcMethods/getConnectedNetwork.ts +++ b/packages/hathor-rpc-handler/src/rpcMethods/getConnectedNetwork.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { HathorWallet } from '@hathor/wallet-lib'; +import type { HathorWallet } from '@hathor/wallet-lib'; import { GetConnectedNetworkRpcRequest, RequestMetadata, diff --git a/packages/hathor-rpc-handler/src/rpcMethods/getUtxos.ts b/packages/hathor-rpc-handler/src/rpcMethods/getUtxos.ts index 1e41d88..e79a37c 100644 --- a/packages/hathor-rpc-handler/src/rpcMethods/getUtxos.ts +++ b/packages/hathor-rpc-handler/src/rpcMethods/getUtxos.ts @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. */ -import { HathorWallet } from '@hathor/wallet-lib'; +import type { HathorWallet } from '@hathor/wallet-lib'; import { GetUtxosConfirmationResponse, GetUtxosRpcRequest, diff --git a/packages/hathor-rpc-handler/src/rpcMethods/index.ts b/packages/hathor-rpc-handler/src/rpcMethods/index.ts index 004d7af..daa5577 100644 --- a/packages/hathor-rpc-handler/src/rpcMethods/index.ts +++ b/packages/hathor-rpc-handler/src/rpcMethods/index.ts @@ -4,3 +4,4 @@ export * from './getUtxos'; export * from './sendNanoContractTx'; export * from './signWithAddress'; export * from './getConnectedNetwork'; +export * from './signOracleData'; diff --git a/packages/hathor-rpc-handler/src/rpcMethods/sendNanoContractTx.ts b/packages/hathor-rpc-handler/src/rpcMethods/sendNanoContractTx.ts index 6e12f90..5f319d1 100644 --- a/packages/hathor-rpc-handler/src/rpcMethods/sendNanoContractTx.ts +++ b/packages/hathor-rpc-handler/src/rpcMethods/sendNanoContractTx.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { HathorWallet } from '@hathor/wallet-lib'; +import type { HathorWallet } from '@hathor/wallet-lib'; import { TriggerTypes, PinConfirmationPrompt, diff --git a/packages/hathor-rpc-handler/src/rpcMethods/signOracleData.ts b/packages/hathor-rpc-handler/src/rpcMethods/signOracleData.ts new file mode 100644 index 0000000..015213b --- /dev/null +++ b/packages/hathor-rpc-handler/src/rpcMethods/signOracleData.ts @@ -0,0 +1,82 @@ +/** + * Copyright (c) Hathor Labs and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { + HathorWallet, + nanoUtils, + bufferUtils, + NanoContractSerializer, +} from '@hathor/wallet-lib'; +import { + TriggerHandler, + RequestMetadata, + SignOracleDataRpcRequest, + PinRequestResponse, + PinConfirmationPrompt, + TriggerTypes, + RpcResponseTypes, + SignOracleDataResponse, + SignOracleDataConfirmationPrompt, + SignOracleDataConfirmationResponse, +} from '../types'; +import { validateNetwork } from '../helpers'; +import { PromptRejectedError } from '../errors'; + +export async function signOracleData( + rpcRequest: SignOracleDataRpcRequest, + wallet: HathorWallet, + requestMetadata: RequestMetadata, + promptHandler: TriggerHandler, +) { + const { network, oracle, data } = rpcRequest.params; + + validateNetwork(wallet, network); + + const prompt: SignOracleDataConfirmationPrompt = { + type: TriggerTypes.SignOracleDataConfirmationPrompt, + method: rpcRequest.method, + data: { + oracle, + data, + } + }; + + const signResponse = await promptHandler(prompt, requestMetadata) as SignOracleDataConfirmationResponse; + + if (!signResponse.data) { + throw new PromptRejectedError('User rejected sign oracle data prompt'); + } + + const pinPrompt: PinConfirmationPrompt = { + type: TriggerTypes.PinConfirmationPrompt, + method: rpcRequest.method, + }; + + const pinResponse = await promptHandler(pinPrompt, requestMetadata) as PinRequestResponse; + + if (!pinResponse.data.accepted) { + throw new PromptRejectedError('User rejected PIN prompt'); + } + + const oracleData = nanoUtils.getOracleBuffer(oracle, wallet.getNetworkObject()); + const nanoSerializer = new NanoContractSerializer(); + const dataSerialized = nanoSerializer.serializeFromType(data, 'str'); + + // TODO getOracleInputData method should be able to receive the PIN as optional parameter as well + wallet.pinCode = pinResponse.data.pinCode; + const inputData = await nanoUtils.getOracleInputData(oracleData, dataSerialized, wallet); + const signature = `${bufferUtils.bufferToHex(inputData)},${data},str`; + + return { + type: RpcResponseTypes.SignOracleDataResponse, + response: { + data, + signature, + oracle, + } + } as SignOracleDataResponse; +} diff --git a/packages/hathor-rpc-handler/src/rpcRequest/index.ts b/packages/hathor-rpc-handler/src/rpcRequest/index.ts new file mode 100644 index 0000000..786b061 --- /dev/null +++ b/packages/hathor-rpc-handler/src/rpcRequest/index.ts @@ -0,0 +1,3 @@ +export * from './sendNanoContractTx'; +export * from './signWithAddress'; +export * from './signOracleData'; diff --git a/packages/hathor-rpc-handler/src/rpcRequest/sendNanoContractTx.ts b/packages/hathor-rpc-handler/src/rpcRequest/sendNanoContractTx.ts new file mode 100644 index 0000000..82360d1 --- /dev/null +++ b/packages/hathor-rpc-handler/src/rpcRequest/sendNanoContractTx.ts @@ -0,0 +1,33 @@ +/** + * Copyright (c) Hathor Labs and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import type { NanoContractAction } from '@hathor/wallet-lib/lib/nano_contracts/types'; +import { + RpcMethods, + SendNanoContractRpcRequest, +} from '../types'; + +export function sendNanoContractTxRpcRequest( + method: string, + blueprintId: string, + actions: NanoContractAction[], + args: unknown[], + pushTx: boolean, + ncId: string | null, +): SendNanoContractRpcRequest { + return { + method: RpcMethods.SendNanoContractTx, + params: { + method, + blueprint_id: blueprintId, + actions, + args, + push_tx: pushTx, + nc_id: ncId, + } + }; +} diff --git a/packages/hathor-rpc-handler/src/rpcRequest/signOracleData.ts b/packages/hathor-rpc-handler/src/rpcRequest/signOracleData.ts new file mode 100644 index 0000000..724df4a --- /dev/null +++ b/packages/hathor-rpc-handler/src/rpcRequest/signOracleData.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) Hathor Labs and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { + RpcMethods, + SignOracleDataRpcRequest, +} from '../types'; + +export function signOracleDataRpcRequest( + network: string, + data: string, + oracle: string, +): SignOracleDataRpcRequest { + return { + method: RpcMethods.SignOracleData, + params: { + network, + data, + oracle, + } + }; +} diff --git a/packages/hathor-rpc-handler/src/rpcRequest/signWithAddress.ts b/packages/hathor-rpc-handler/src/rpcRequest/signWithAddress.ts new file mode 100644 index 0000000..e839efe --- /dev/null +++ b/packages/hathor-rpc-handler/src/rpcRequest/signWithAddress.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) Hathor Labs and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import { + RpcMethods, + SignWithAddressRpcRequest, +} from '../types'; + +export function signWithAddressRpcRequest( + network: string, + message: string, + addressIndex: number, +): SignWithAddressRpcRequest { + return { + method: RpcMethods.SignWithAddress, + params: { + network, + message, + addressIndex, + } + }; +} diff --git a/packages/hathor-rpc-handler/src/types/prompt.ts b/packages/hathor-rpc-handler/src/types/prompt.ts index 4c8e32c..0ca8815 100644 --- a/packages/hathor-rpc-handler/src/types/prompt.ts +++ b/packages/hathor-rpc-handler/src/types/prompt.ts @@ -26,6 +26,7 @@ export enum TriggerTypes { LoadingFinishedTrigger, CreateTokenConfirmationPrompt, CreateTokenLoadingTrigger, + SignOracleDataConfirmationPrompt, } export enum TriggerResponseTypes { @@ -35,6 +36,7 @@ export enum TriggerResponseTypes { SignMessageWithAddressConfirmationResponse, SendNanoContractTxConfirmationResponse, CreateTokenConfirmationResponse, + SignOracleDataConfirmationResponse, } export type Trigger = @@ -54,7 +56,8 @@ export type Trigger = | LoadingFinishedTrigger | CreateTokenConfirmationPrompt | CreateTokenLoadingTrigger - | CreateTokenLoadingFinishedTrigger; + | CreateTokenLoadingFinishedTrigger + | SignOracleDataConfirmationPrompt; export interface BaseLoadingTrigger { type: TriggerTypes; @@ -109,6 +112,14 @@ export interface GetUtxosConfirmationPrompt extends BaseConfirmationPrompt { data: UtxoDetails[]; } +export interface SignOracleDataConfirmationPrompt extends BaseConfirmationPrompt { + type: TriggerTypes.SignOracleDataConfirmationPrompt; + data: { + oracle: string; + data: string; + } +} + export interface SignMessageWithAddressConfirmationPrompt extends BaseConfirmationPrompt { type: TriggerTypes.SignMessageWithAddressConfirmationPrompt; data: { @@ -220,13 +231,19 @@ export interface SignMessageWithAddressConfirmationResponse { data: boolean; } +export interface SignOracleDataConfirmationResponse { + type: TriggerResponseTypes.SignOracleDataConfirmationResponse; + data: boolean; +} + export type TriggerResponse = AddressRequestClientResponse | GetUtxosConfirmationResponse | PinRequestResponse | SignMessageWithAddressConfirmationResponse | SendNanoContractTxConfirmationResponse - | CreateTokenConfirmationResponse; + | CreateTokenConfirmationResponse + | SignOracleDataConfirmationResponse; export type TriggerHandler = (prompt: Trigger, requestMetadata: RequestMetadata) => Promise; diff --git a/packages/hathor-rpc-handler/src/types/rpcRequest.ts b/packages/hathor-rpc-handler/src/types/rpcRequest.ts index 54c59fa..cd22ec6 100644 --- a/packages/hathor-rpc-handler/src/types/rpcRequest.ts +++ b/packages/hathor-rpc-handler/src/types/rpcRequest.ts @@ -17,35 +17,30 @@ export enum RpcMethods { PushTxHex = 'htr_pushTxHex', GetOperationStatus = 'htr_getOperationStatus', SendNanoContractTx = 'htr_sendNanoContractTx', + SignOracleData = 'htr_signOracleData', } -export interface BaseRpcRequest { - method: string; - id: string; - jsonrpc: string; -} - -export interface CreateTokenRpcRequest extends BaseRpcRequest { +export interface CreateTokenRpcRequest { method: RpcMethods.CreateToken, params: { name: string; symbol: string; amount: number; - address: string | null; - change_address: string | null; + address?: string; + change_address?: string; create_mint: boolean; - mint_authority_address: string | null; + mint_authority_address?: string; allow_external_mint_authority_address?: boolean; create_melt: boolean; - melt_authority_address: string | null; + melt_authority_address?: string; allow_external_melt_authority_address?: boolean; push_tx: boolean; network: string; - data: string[] | null; + data?: string[]; } } -export interface GetAddressRpcRequest extends BaseRpcRequest { +export interface GetAddressRpcRequest { method: RpcMethods.GetAddress, params: { type: 'first_empty' | 'full_path' | 'index' | 'client'; @@ -55,7 +50,7 @@ export interface GetAddressRpcRequest extends BaseRpcRequest { } } -export interface GetBalanceRpcRequest extends BaseRpcRequest { +export interface GetBalanceRpcRequest { method: RpcMethods.GetBalance, params: { network: string; @@ -64,7 +59,7 @@ export interface GetBalanceRpcRequest extends BaseRpcRequest { }; } -export interface GetUtxosRpcRequest extends BaseRpcRequest { +export interface GetUtxosRpcRequest { method: RpcMethods.GetUtxos, params: { network: string; @@ -79,7 +74,7 @@ export interface GetUtxosRpcRequest extends BaseRpcRequest { }; } -export interface SignWithAddressRpcRequest extends BaseRpcRequest { +export interface SignWithAddressRpcRequest { method: RpcMethods.SignWithAddress, params: { network: string; @@ -88,7 +83,16 @@ export interface SignWithAddressRpcRequest extends BaseRpcRequest { } } -export interface SendNanoContractRpcRequest extends BaseRpcRequest { +export interface SignOracleDataRpcRequest { + method: RpcMethods.SignOracleData, + params: { + network: string; + data: string; + oracle: string; + } +} + +export interface SendNanoContractRpcRequest { method: RpcMethods.SendNanoContractTx, params: { method: string; @@ -104,11 +108,12 @@ export type RequestMetadata = { [key: string]: string, }; -export interface GetConnectedNetworkRpcRequest extends BaseRpcRequest { +export interface GetConnectedNetworkRpcRequest { method: RpcMethods.GetConnectedNetwork, } -export interface GenericRpcRequest extends BaseRpcRequest { +export interface GenericRpcRequest { + method: string; params?: unknown | null; } @@ -118,4 +123,6 @@ export type RpcRequest = GetAddressRpcRequest | SignWithAddressRpcRequest | SendNanoContractRpcRequest | GetConnectedNetworkRpcRequest - | GenericRpcRequest; + | GenericRpcRequest + | SignOracleDataRpcRequest; + diff --git a/packages/hathor-rpc-handler/src/types/rpcResponse.ts b/packages/hathor-rpc-handler/src/types/rpcResponse.ts index 232810a..f05e48a 100644 --- a/packages/hathor-rpc-handler/src/types/rpcResponse.ts +++ b/packages/hathor-rpc-handler/src/types/rpcResponse.ts @@ -5,7 +5,7 @@ * LICENSE file in the root directory of this source tree. */ -import { CreateTokenTransaction, SendTransaction, Transaction } from '@hathor/wallet-lib'; +import { CreateTokenTransaction, SendTransaction } from '@hathor/wallet-lib'; import NanoContract from '@hathor/wallet-lib/lib/nano_contracts/nano_contract'; import { AddressInfoObject, GetBalanceObject } from '@hathor/wallet-lib/lib/wallet/types'; import { UtxoDetails } from './prompt'; @@ -18,6 +18,7 @@ export enum RpcResponseTypes { GetConnectedNetworkResponse, GetUtxosResponse, CreateTokenResponse, + SignOracleDataResponse } export interface BaseRpcResponse { @@ -61,6 +62,15 @@ export interface GetConnectedNetworkResponse extends BaseRpcResponse { } } +export interface SignOracleDataResponse extends BaseRpcResponse { + type: RpcResponseTypes.SignOracleDataResponse; + response: { + data: string; + signature: string; + oracle: string; + } +} + export interface GetUtxosResponse extends BaseRpcResponse { type: RpcResponseTypes.GetUtxosResponse; response: UtxoDetails[]; @@ -72,4 +82,5 @@ export type RpcResponse = GetAddressResponse | GetBalanceResponse | GetConnectedNetworkResponse | CreateTokenResponse + | SignOracleDataResponse | GetUtxosResponse; diff --git a/yarn.lock b/yarn.lock index 78e7c93..9515973 100644 --- a/yarn.lock +++ b/yarn.lock @@ -472,6 +472,23 @@ __metadata: languageName: node linkType: hard +"@hathor/hathor-rpc-handler@workspace:packages/hathor-rpc-handler": + version: 0.0.0-use.local + resolution: "@hathor/hathor-rpc-handler@workspace:packages/hathor-rpc-handler" + dependencies: + "@eslint/js": "npm:9.4.0" + "@hathor/wallet-lib": "npm:1.8.0" + "@types/eslint__js": "npm:8.42.3" + "@types/jest": "npm:29.5.12" + "@types/node": "npm:20.14.2" + eslint: "npm:9.4.0" + jest: "npm:29.7.0" + ts-jest: "npm:29.1.4" + typescript: "npm:5.4.5" + typescript-eslint: "npm:7.13.0" + languageName: unknown + linkType: soft + "@hathor/wallet-lib@npm:1.8.0": version: 1.8.0 resolution: "@hathor/wallet-lib@npm:1.8.0" @@ -2580,23 +2597,6 @@ __metadata: languageName: node linkType: hard -"hathor-rpc-handler@workspace:packages/hathor-rpc-handler": - version: 0.0.0-use.local - resolution: "hathor-rpc-handler@workspace:packages/hathor-rpc-handler" - dependencies: - "@eslint/js": "npm:9.4.0" - "@hathor/wallet-lib": "npm:1.8.0" - "@types/eslint__js": "npm:8.42.3" - "@types/jest": "npm:29.5.12" - "@types/node": "npm:20.14.2" - eslint: "npm:9.4.0" - jest: "npm:29.7.0" - ts-jest: "npm:29.1.4" - typescript: "npm:5.4.5" - typescript-eslint: "npm:7.13.0" - languageName: unknown - linkType: soft - "hathor-rpc-lib@workspace:.": version: 0.0.0-use.local resolution: "hathor-rpc-lib@workspace:."