From 3debd475b1a2f2d77c0100233db2b483f0405384 Mon Sep 17 00:00:00 2001 From: khanti42 Date: Thu, 21 Nov 2024 15:35:38 +0100 Subject: [PATCH] feat: replace rpc dialogs with jsx (#422) * chore: add jsx support in snap (#415) * chore: add jsx support in snap * chore: fix comment * chore: update yarn.lock * feat: add common jsx component (#417) * chore: add jsx support in snap * chore: add jsx support in snap * feat: jsx support management * feat: common jsx components and fragments * chore: fix test and lint * chore: rollback jsx support detection not here * chore: fix comment * chore: fix comments * chore: fix comments * chore: update yarn.lock * chore: update yarn.lock * chore: add react-dom types * chore: fix comments * chore: rebase wallet-ui changes happening elsewhere * feat: add fee token selection interactive UI (#418) * chore: add jsx support in snap * chore: add jsx support in snap * feat: jsx support management * feat: common jsx components and fragments * chore: fix test and lint * feat: add interactive-ui for execute txn * fix: add mutex in jsx support detection mechanism * chore: fixture request addapted * chore: ensure test pass * chore: lint * chore: remove console.log * chore: rollback jsx support detection not here * chore: rollback index.tsx * chore: fix comment * chore: fix comments * chore: fix comments * chore: update yarn.lock * chore: update yarn.lock * chore: update yarn.lock * chore: rebase wallet-ui changes happening elsewhere * chore: fix comments * chore: fix comments * chore: fix comments * chore: fix comments * chore: fix comments * chore: fix comments * chore: removed utils * fix: implement comments * fix: implement comments * chore: fix comments * Update packages/starknet-snap/src/ui/components/ExecuteTxnUI.tsx Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> * Update packages/starknet-snap/src/ui/components/ExecuteTxnUI.tsx Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> * Update packages/starknet-snap/src/ui/utils.test.tsx Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> * Update packages/starknet-snap/src/types/snapState.ts Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> * Update packages/starknet-snap/src/ui/components/ExecuteTxnUI.tsx Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> * Update packages/starknet-snap/src/ui/components/ExecuteTxnUI.tsx Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> * Update packages/starknet-snap/src/ui/utils.test.tsx Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> * Update packages/starknet-snap/src/ui/utils.test.tsx Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> * chore: fix comments * chore: fix comments * chore: fix comments * chore: lint --------- Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> * feat: adopt fee token selection dialog in RPC `starkNet_executeTxn` (#419) * chore: add jsx support in snap * chore: add jsx support in snap * feat: jsx support management * feat: common jsx components and fragments * chore: fix test and lint * feat: add interactive-ui for execute txn * fix: add mutex in jsx support detection mechanism * chore: fixture request addapted * chore: ensure test pass * chore: lint * feat: add interactive-ui in execute txn * chore: remove console.log * chore: missing helper in tests * chore: lint * chore: rollback jsx support detection not here * chore: rollback index.tsx * chore: fix comment * chore: fix comments * chore: fix comments * chore: update yarn.lock * chore: update yarn.lock * feat: update wallet-ui message * chore: update yarn.lock * chore: rebase wallet-ui changes happening elsewhere * chore: rebase wallet-ui changes happening elsewhere * chore: fix comments * chore: fix comments * fix: formatter-utils * chore: fix comments * chore: fix comments * chore: lint * chore: lint * chore: fix comments * chore: fix comments * chore: fix comments * chore: fix comments * chore: removed utils * fix: implement comments * fix: implement comments * chore: fix comments * chore: fix comments * Update packages/starknet-snap/src/ui/components/ExecuteTxnUI.tsx Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> * Update packages/starknet-snap/src/ui/components/ExecuteTxnUI.tsx Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> * Update packages/starknet-snap/src/ui/utils.test.tsx Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> * Update packages/starknet-snap/src/types/snapState.ts Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> * Update packages/starknet-snap/src/ui/components/ExecuteTxnUI.tsx Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> * Update packages/starknet-snap/src/ui/components/ExecuteTxnUI.tsx Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> * Update packages/starknet-snap/src/ui/utils.test.tsx Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> * Update packages/starknet-snap/src/ui/utils.test.tsx Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> * chore: fix comments * chore: fix comments * chore: fix comments * chore: fix comments * chore: fix comments * chore: lint * chore: lint * chore: fix comments * chore: fix comments --------- Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> * chore: fix tests * feat: replace rpc dialogs with jsx * feat: add sign-message in jsx * feat: add jsx support detection (#416) * chore: add jsx support in snap * chore: add jsx support in snap * feat: jsx support management * chore: fix test and lint * fix: add mutex in jsx support detection mechanism * chore: ensure test pass * feat: new init state manager class to manage state init and support check * fix: wait for hooks in request handler * chore: lint * fix: set jsx support to true before showing dialog * chore: fix comment * fix: moved ping pong * chore: lint * chore: rollback state * chore: lint * chore: fix comments * fix: test suits * fix: use functional component * feat: add event listener for fee token selection (#420) * chore: add jsx support in snap * chore: add jsx support in snap * feat: jsx support management * feat: common jsx components and fragments * chore: fix test and lint * feat: add interactive-ui for execute txn * fix: add mutex in jsx support detection mechanism * chore: fixture request addapted * chore: ensure test pass * chore: lint * feat: add interactive-ui in execute txn * chore: remove console.log * chore: missing helper in tests * chore: lint * feat: event-handler in index.tsx * feat: event controller * feat: error handling and tests suits for event controller * fix: test suits * fix: signer in fee-token-selector * chore: rollback jsx support detection not here * chore: rollback index.tsx * chore: lint * chore: fix comment * chore: fix comments * chore: fix comments * chore: update yarn.lock * chore: update yarn.lock * feat: update wallet-ui message * chore: update yarn.lock * chore: rebase wallet-ui changes happening elsewhere * chore: rebase wallet-ui changes happening elsewhere * chore: fix comments * chore: fix comments * fix: formatter-utils * chore: fix comments * chore: fix comments * chore: lint * chore: lint * chore: fix comments * chore: fix comments * chore: fix comments * chore: fix comments * chore: lint * chore: lint * chore: removed utils * feat: add comments in user-input classes * fix: implement comments * fix: implement comments * chore: fix comments * chore: fix comments * Update packages/starknet-snap/src/ui/components/ExecuteTxnUI.tsx Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> * Update packages/starknet-snap/src/ui/components/ExecuteTxnUI.tsx Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> * Update packages/starknet-snap/src/ui/utils.test.tsx Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> * Update packages/starknet-snap/src/types/snapState.ts Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> * Update packages/starknet-snap/src/ui/components/ExecuteTxnUI.tsx Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> * Update packages/starknet-snap/src/ui/components/ExecuteTxnUI.tsx Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> * Update packages/starknet-snap/src/ui/utils.test.tsx Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> * Update packages/starknet-snap/src/ui/utils.test.tsx Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> * chore: fix comments * chore: fix comments * chore: fix comments * chore: fix comments * chore: fix comments * chore: lint * chore: lint * chore: fix rebase * chore: fix comments * chore: fix comments * chore: fix comments * chore: rebase * fix: removed user-input controller abstract class and derived ones * chore: rollback execute-txn * chore: update * chore: fix comments * chore: fix comments * chore: refine the code * fix: add execution test * fix: update execute txn test --------- Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> * chore: lint * chore: docstring * chore: lint * chore: docstring * chore: lint * chore: lint * chore: lint --------- Co-authored-by: Stanley Yuen <102275989+stanleyyconsensys@users.noreply.github.com> --- .../src/rpcs/__tests__/helper.ts | 86 +++++++++++ .../src/rpcs/display-private-key.test.ts | 31 ++-- .../src/rpcs/display-private-key.ts | 19 +-- .../src/rpcs/sign-declare-transaction.test.ts | 24 ++- .../src/rpcs/sign-declare-transaction.ts | 45 ++---- .../src/rpcs/sign-message.test.ts | 24 ++- .../starknet-snap/src/rpcs/sign-message.ts | 34 +---- .../src/rpcs/sign-transaction.test.ts | 27 ++-- .../src/rpcs/sign-transaction.ts | 39 +---- .../src/rpcs/switch-network.test.ts | 21 +-- .../starknet-snap/src/rpcs/switch-network.ts | 40 +---- .../src/rpcs/watch-asset.test.ts | 38 ++--- .../starknet-snap/src/rpcs/watch-asset.ts | 78 +--------- .../src/ui/components/DisplayPrivateKeyUI.tsx | 47 ++++++ .../components/SignDeclareTransactionUI.tsx | 41 +++++ .../src/ui/components/SignMessageUI.tsx | 37 +++++ .../src/ui/components/SignTransactionUI.tsx | 41 +++++ .../src/ui/components/SwitchNetworkUI.tsx | 33 +++++ .../src/ui/components/WatchAssetUI.tsx | 62 ++++++++ packages/starknet-snap/src/ui/utils.tsx | 140 ++++++++++++++++++ 20 files changed, 588 insertions(+), 319 deletions(-) create mode 100644 packages/starknet-snap/src/ui/components/DisplayPrivateKeyUI.tsx create mode 100644 packages/starknet-snap/src/ui/components/SignDeclareTransactionUI.tsx create mode 100644 packages/starknet-snap/src/ui/components/SignMessageUI.tsx create mode 100644 packages/starknet-snap/src/ui/components/SignTransactionUI.tsx create mode 100644 packages/starknet-snap/src/ui/components/SwitchNetworkUI.tsx create mode 100644 packages/starknet-snap/src/ui/components/WatchAssetUI.tsx diff --git a/packages/starknet-snap/src/rpcs/__tests__/helper.ts b/packages/starknet-snap/src/rpcs/__tests__/helper.ts index 5f3f2665..8d20363f 100644 --- a/packages/starknet-snap/src/rpcs/__tests__/helper.ts +++ b/packages/starknet-snap/src/rpcs/__tests__/helper.ts @@ -5,6 +5,7 @@ import type { StarknetAccount } from '../../__tests__/helper'; import { generateAccounts, generateRandomValue } from '../../__tests__/helper'; import { TransactionRequestStateManager } from '../../state/request-state-manager'; import type { SnapState } from '../../types/snapState'; +import * as snapUiUtils from '../../ui/utils'; import { getExplorerUrl, shortenAddress, toJson } from '../../utils'; import * as snapHelper from '../../utils/snap'; import * as snapUtils from '../../utils/snapUtils'; @@ -80,6 +81,91 @@ export function prepareConfirmDialog() { }; } +/** + * + */ +export function prepareRenderWatchAssetUI() { + const confirmDialogSpy = jest.spyOn(snapUiUtils, 'renderWatchAssetUI'); + confirmDialogSpy.mockResolvedValue(true); + return { + confirmDialogSpy, + }; +} + +/** + * + */ +export function prepareRenderSwitchNetworkUI() { + const confirmDialogSpy = jest.spyOn(snapUiUtils, 'renderSwitchNetworkUI'); + confirmDialogSpy.mockResolvedValue(true); + return { + confirmDialogSpy, + }; +} + +/** + * + */ +export function prepareRenderSignMessageUI() { + const confirmDialogSpy = jest.spyOn(snapUiUtils, 'renderSignMessageUI'); + confirmDialogSpy.mockResolvedValue(true); + return { + confirmDialogSpy, + }; +} + +/** + * + */ +export function prepareRenderSignTransactionUI() { + const confirmDialogSpy = jest.spyOn(snapUiUtils, 'renderSignTransactionUI'); + confirmDialogSpy.mockResolvedValue(true); + return { + confirmDialogSpy, + }; +} + +/** + * + */ +export function prepareRenderSignDeclareTransactionUI() { + const confirmDialogSpy = jest.spyOn( + snapUiUtils, + 'renderSignDeclareTransactionUI', + ); + confirmDialogSpy.mockResolvedValue(true); + return { + confirmDialogSpy, + }; +} + +/** + * + */ +export function prepareRenderDisplayPrivateKeyConfirmUI() { + const confirmDialogSpy = jest.spyOn( + snapUiUtils, + 'renderDisplayPrivateKeyConfirmUI', + ); + confirmDialogSpy.mockResolvedValue(true); + return { + confirmDialogSpy, + }; +} + +/** + * + */ +export function prepareRenderDisplayPrivateKeyAlertUI() { + const alertDialogSpy = jest.spyOn( + snapUiUtils, + 'renderDisplayPrivateKeyAlertUI', + ); + return { + alertDialogSpy, + }; +} + /** * */ diff --git a/packages/starknet-snap/src/rpcs/display-private-key.test.ts b/packages/starknet-snap/src/rpcs/display-private-key.test.ts index fc6a17f8..aa0aac00 100644 --- a/packages/starknet-snap/src/rpcs/display-private-key.test.ts +++ b/packages/starknet-snap/src/rpcs/display-private-key.test.ts @@ -8,9 +8,9 @@ import { } from '../utils/exceptions'; import { mockAccount, - prepareAlertDialog, prepareMockAccount, - prepareConfirmDialog, + prepareRenderDisplayPrivateKeyAlertUI, + prepareRenderDisplayPrivateKeyConfirmUI, } from './__tests__/helper'; import { displayPrivateKey } from './display-private-key'; import type { DisplayPrivateKeyParams } from './display-private-key'; @@ -40,49 +40,36 @@ describe('displayPrivateKey', () => { const chainId = constants.StarknetChainId.SN_SEPOLIA; const account = await mockAccount(chainId); prepareMockAccount(account, state); - prepareConfirmDialog(); - const { alertDialogSpy } = prepareAlertDialog(); + prepareRenderDisplayPrivateKeyConfirmUI(); + const { alertDialogSpy } = prepareRenderDisplayPrivateKeyAlertUI(); const request = createRequestParam(chainId, account.address); await displayPrivateKey.execute(request); - expect(alertDialogSpy).toHaveBeenCalledTimes(1); - - const calls = alertDialogSpy.mock.calls[0][0]; - - expect(calls).toStrictEqual([ - { type: 'text', value: 'Starknet Account Private Key' }, - { type: 'copyable', value: account.privateKey }, - ]); + expect(alertDialogSpy).toHaveBeenCalledWith(account.privateKey); }); it('renders confirmation dialog', async () => { const chainId = constants.StarknetChainId.SN_SEPOLIA; const account = await mockAccount(chainId); prepareMockAccount(account, state); - const { confirmDialogSpy } = prepareConfirmDialog(); - prepareAlertDialog(); + const { confirmDialogSpy } = prepareRenderDisplayPrivateKeyConfirmUI(); + prepareRenderDisplayPrivateKeyAlertUI(); const request = createRequestParam(chainId, account.address); await displayPrivateKey.execute(request); expect(confirmDialogSpy).toHaveBeenCalledTimes(1); - - const calls = confirmDialogSpy.mock.calls[0][0]; - - expect(calls).toStrictEqual([ - { type: 'text', value: 'Do you want to export your private key?' }, - ]); }); it('throws `UserRejectedOpError` if user denies the operation', async () => { const chainId = constants.StarknetChainId.SN_SEPOLIA; const account = await mockAccount(chainId); prepareMockAccount(account, state); - const { confirmDialogSpy } = prepareConfirmDialog(); - prepareAlertDialog(); + const { confirmDialogSpy } = prepareRenderDisplayPrivateKeyConfirmUI(); + prepareRenderDisplayPrivateKeyAlertUI(); confirmDialogSpy.mockResolvedValue(false); diff --git a/packages/starknet-snap/src/rpcs/display-private-key.ts b/packages/starknet-snap/src/rpcs/display-private-key.ts index b1dd38ef..bef32db8 100644 --- a/packages/starknet-snap/src/rpcs/display-private-key.ts +++ b/packages/starknet-snap/src/rpcs/display-private-key.ts @@ -1,11 +1,12 @@ -import { copyable, text } from '@metamask/snaps-sdk'; import { type Infer, object, literal, assign } from 'superstruct'; +import { + renderDisplayPrivateKeyAlertUI, + renderDisplayPrivateKeyConfirmUI, +} from '../ui/utils'; import { AccountRpcController, AddressStruct, - confirmDialog, - alertDialog, BaseRequestStruct, } from '../utils'; import { UserRejectedOpError } from '../utils/exceptions'; @@ -56,19 +57,11 @@ export class DisplayPrivateKeyRpc extends AccountRpcController< // eslint-disable-next-line @typescript-eslint/no-unused-vars params: DisplayPrivateKeyParams, ): Promise { - const confirmComponents = [text('Do you want to export your private key?')]; - - if (!(await confirmDialog(confirmComponents))) { + if (!(await renderDisplayPrivateKeyConfirmUI())) { throw new UserRejectedOpError() as unknown as Error; } - const alertComponents = [ - text('Starknet Account Private Key'), - copyable(this.account.privateKey), - ]; - - await alertDialog(alertComponents); - + await renderDisplayPrivateKeyAlertUI(this.account.privateKey); return null; } } diff --git a/packages/starknet-snap/src/rpcs/sign-declare-transaction.test.ts b/packages/starknet-snap/src/rpcs/sign-declare-transaction.test.ts index a36f639c..37db7d59 100644 --- a/packages/starknet-snap/src/rpcs/sign-declare-transaction.test.ts +++ b/packages/starknet-snap/src/rpcs/sign-declare-transaction.test.ts @@ -11,10 +11,7 @@ import * as starknetUtils from '../utils/starknetUtils'; import { mockAccount, prepareMockAccount, - prepareConfirmDialog, - buildSignerComponent, - buildNetworkComponent, - buildJsonDataComponent, + prepareRenderSignDeclareTransactionUI, } from './__tests__/helper'; import { signDeclareTransaction } from './sign-declare-transaction'; import type { SignDeclareTransactionParams } from './sign-declare-transaction'; @@ -52,7 +49,7 @@ describe('signDeclareTransaction', () => { const account = await mockAccount(chainId); prepareMockAccount(account, state); - prepareConfirmDialog(); + prepareRenderSignDeclareTransactionUI(); const request = createRequest(chainId, account.address); @@ -72,19 +69,18 @@ describe('signDeclareTransaction', () => { const { address } = account; prepareMockAccount(account, state); - const { confirmDialogSpy } = prepareConfirmDialog(); + const { confirmDialogSpy } = prepareRenderSignDeclareTransactionUI(); const request = createRequest(chainId, address); await signDeclareTransaction.execute(request); - const calls = confirmDialogSpy.mock.calls[0][0]; - expect(calls).toStrictEqual([ - { type: 'heading', value: 'Do you want to sign this transaction?' }, - buildSignerComponent(address, chainId), - buildNetworkComponent(STARKNET_SEPOLIA_TESTNET_NETWORK.name), - buildJsonDataComponent('Declare Transaction Details', request.details), - ]); + expect(confirmDialogSpy).toHaveBeenCalledWith({ + senderAddress: address, + chainId, + networkName: STARKNET_SEPOLIA_TESTNET_NETWORK.name, + declareTransactions: request.details, + }); }); it('throws `UserRejectedOpError` if user denied the operation', async () => { @@ -92,7 +88,7 @@ describe('signDeclareTransaction', () => { const account = await mockAccount(chainId); prepareMockAccount(account, state); - const { confirmDialogSpy } = prepareConfirmDialog(); + const { confirmDialogSpy } = prepareRenderSignDeclareTransactionUI(); confirmDialogSpy.mockResolvedValue(false); diff --git a/packages/starknet-snap/src/rpcs/sign-declare-transaction.ts b/packages/starknet-snap/src/rpcs/sign-declare-transaction.ts index 775dcd09..aa1fbf15 100644 --- a/packages/starknet-snap/src/rpcs/sign-declare-transaction.ts +++ b/packages/starknet-snap/src/rpcs/sign-declare-transaction.ts @@ -1,19 +1,14 @@ -import type { Component } from '@metamask/snaps-sdk'; import type { DeclareSignerDetails } from 'starknet'; import type { Infer } from 'superstruct'; import { array, object, string, assign } from 'superstruct'; +import { renderSignDeclareTransactionUI } from '../ui/utils'; import { - confirmDialog, AddressStruct, BaseRequestStruct, AccountRpcController, DeclareSignDetailsStruct, mapDeprecatedParams, - signerUI, - networkUI, - jsonDataUI, - headerUI, } from '../utils'; import { UserRejectedOpError } from '../utils/exceptions'; import { signDeclareTransaction as signDeclareTransactionUtil } from '../utils/starknetUtils'; @@ -84,7 +79,14 @@ export class SignDeclareTransactionRpc extends AccountRpcController< params: SignDeclareTransactionParams, ): Promise { const { details } = params; - if (!(await this.getSignDeclareTransactionConsensus(details))) { + if ( + !(await renderSignDeclareTransactionUI({ + senderAddress: details.senderAddress, + networkName: this.network.name, + chainId: this.network.chainId, + declareTransactions: details, + })) + ) { throw new UserRejectedOpError() as unknown as Error; } @@ -93,35 +95,6 @@ export class SignDeclareTransactionRpc extends AccountRpcController< details as unknown as DeclareSignerDetails, )) as unknown as SignDeclareTransactionResponse; } - - protected async getSignDeclareTransactionConsensus( - details: Infer, - ) { - const components: Component[] = []; - components.push(headerUI('Do you want to sign this transaction?')); - - components.push( - signerUI({ - address: details.senderAddress, - chainId: this.network.chainId, - }), - ); - - components.push( - networkUI({ - networkName: this.network.name, - }), - ); - - components.push( - jsonDataUI({ - label: 'Declare Transaction Details', - data: details, - }), - ); - - return await confirmDialog(components); - } } export const signDeclareTransaction = new SignDeclareTransactionRpc(); diff --git a/packages/starknet-snap/src/rpcs/sign-message.test.ts b/packages/starknet-snap/src/rpcs/sign-message.test.ts index 9b34d629..24a3c4ae 100644 --- a/packages/starknet-snap/src/rpcs/sign-message.test.ts +++ b/packages/starknet-snap/src/rpcs/sign-message.test.ts @@ -11,9 +11,7 @@ import * as starknetUtils from '../utils/starknetUtils'; import { mockAccount, prepareMockAccount, - prepareConfirmDialog, - buildSignerComponent, - buildJsonDataComponent, + prepareRenderSignMessageUI, } from './__tests__/helper'; import { signMessage } from './sign-message'; import type { SignMessageParams } from './sign-message'; @@ -33,7 +31,7 @@ describe('signMessage', () => { const account = await mockAccount(constants.StarknetChainId.SN_SEPOLIA); prepareMockAccount(account, state); - prepareConfirmDialog(); + prepareRenderSignMessageUI(); const expectedResult = await starknetUtils.signMessage( account.privateKey, @@ -56,30 +54,28 @@ describe('signMessage', () => { const { address, chainId } = account; prepareMockAccount(account, state); - const { confirmDialogSpy } = prepareConfirmDialog(); + const { confirmDialogSpy } = prepareRenderSignMessageUI(); const request = { - chainId: constants.StarknetChainId.SN_SEPOLIA, + chainId: chainId as constants.StarknetChainId, address, typedDataMessage: typedDataExample, enableAuthorize: true, }; await signMessage.execute(request); - - const calls = confirmDialogSpy.mock.calls[0][0]; - expect(calls).toStrictEqual([ - { type: 'heading', value: 'Do you want to sign this message?' }, - buildSignerComponent(address, chainId), - buildJsonDataComponent('Message', typedDataExample), - ]); + expect(confirmDialogSpy).toHaveBeenCalledWith({ + address, + chainId, + typedDataMessage: typedDataExample, + }); }); it('throws `UserRejectedOpError` if user denied the operation', async () => { const account = await mockAccount(constants.StarknetChainId.SN_SEPOLIA); prepareMockAccount(account, state); - const { confirmDialogSpy } = prepareConfirmDialog(); + const { confirmDialogSpy } = prepareRenderSignMessageUI(); confirmDialogSpy.mockResolvedValue(false); diff --git a/packages/starknet-snap/src/rpcs/sign-message.ts b/packages/starknet-snap/src/rpcs/sign-message.ts index 80112ca3..6b4387cb 100644 --- a/packages/starknet-snap/src/rpcs/sign-message.ts +++ b/packages/starknet-snap/src/rpcs/sign-message.ts @@ -1,18 +1,14 @@ -import type { Component } from '@metamask/snaps-sdk'; import type { Infer } from 'superstruct'; import { array, object, string, assign } from 'superstruct'; +import { renderSignMessageUI } from '../ui/utils'; import { - confirmDialog, AddressStruct, TypeDataStruct, AuthorizableStruct, BaseRequestStruct, AccountRpcController, mapDeprecatedParams, - signerUI, - jsonDataUI, - headerUI, } from '../utils'; import { UserRejectedOpError } from '../utils/exceptions'; import { signMessage as signMessageUtil } from '../utils/starknetUtils'; @@ -79,7 +75,11 @@ export class SignMessageRpc extends AccountRpcController< // Get Starknet expected not to show the confirm dialog, therefore, `enableAuthorize` will set to false to bypass the confirmation // TODO: enableAuthorize should set default to true enableAuthorize && - !(await this.getSignMessageConsensus(typedDataMessage, address)) + !(await renderSignMessageUI({ + address, + typedDataMessage, + chainId: this.network.chainId, + })) ) { throw new UserRejectedOpError() as unknown as Error; } @@ -90,28 +90,6 @@ export class SignMessageRpc extends AccountRpcController< address, ); } - - protected async getSignMessageConsensus( - typedDataMessage: Infer, - address: string, - ) { - const components: Component[] = []; - components.push(headerUI('Do you want to sign this message?')); - components.push( - signerUI({ - address, - chainId: this.network.chainId, - }), - ); - components.push( - jsonDataUI({ - label: 'Message', - data: typedDataMessage, - }), - ); - - return await confirmDialog(components); - } } export const signMessage = new SignMessageRpc(); diff --git a/packages/starknet-snap/src/rpcs/sign-transaction.test.ts b/packages/starknet-snap/src/rpcs/sign-transaction.test.ts index 5200545a..66dbdc7c 100644 --- a/packages/starknet-snap/src/rpcs/sign-transaction.test.ts +++ b/packages/starknet-snap/src/rpcs/sign-transaction.test.ts @@ -12,10 +12,7 @@ import * as starknetUtils from '../utils/starknetUtils'; import { mockAccount, prepareMockAccount, - prepareConfirmDialog, - buildNetworkComponent, - buildSignerComponent, - buildJsonDataComponent, + prepareRenderSignTransactionUI, } from './__tests__/helper'; import { signTransaction } from './sign-transaction'; import type { SignTransactionParams } from './sign-transaction'; @@ -52,7 +49,7 @@ describe('signTransaction', () => { const chainId = constants.StarknetChainId.SN_SEPOLIA; const account = await mockAccount(chainId); prepareMockAccount(account, state); - prepareConfirmDialog(); + prepareRenderSignTransactionUI(); const request = createRequestParam(chainId, account.address); const expectedResult = await starknetUtils.signTransactions( @@ -71,25 +68,23 @@ describe('signTransaction', () => { const account = await mockAccount(chainId); const { address } = account; prepareMockAccount(account, state); - const { confirmDialogSpy } = prepareConfirmDialog(); + const { confirmDialogSpy } = prepareRenderSignTransactionUI(); const request = createRequestParam(chainId, account.address, true); await signTransaction.execute(request); - - const calls = confirmDialogSpy.mock.calls[0][0]; - expect(calls).toStrictEqual([ - { type: 'heading', value: 'Do you want to sign this transaction?' }, - buildSignerComponent(address, chainId), - buildNetworkComponent(STARKNET_SEPOLIA_TESTNET_NETWORK.name), - buildJsonDataComponent('Transaction', transactionExample.transactions), - ]); + expect(confirmDialogSpy).toHaveBeenCalledWith({ + senderAddress: address, + chainId, + networkName: STARKNET_SEPOLIA_TESTNET_NETWORK.name, + transactions: request.transactions, + }); }); it('does not render the confirmation dialog if enableAuthorize is false', async () => { const chainId = constants.StarknetChainId.SN_SEPOLIA; const account = await mockAccount(chainId); prepareMockAccount(account, state); - const { confirmDialogSpy } = prepareConfirmDialog(); + const { confirmDialogSpy } = prepareRenderSignTransactionUI(); const request = createRequestParam(chainId, account.address, false); await signTransaction.execute(request); @@ -101,7 +96,7 @@ describe('signTransaction', () => { const chainId = constants.StarknetChainId.SN_SEPOLIA; const account = await mockAccount(chainId); prepareMockAccount(account, state); - const { confirmDialogSpy } = prepareConfirmDialog(); + const { confirmDialogSpy } = prepareRenderSignTransactionUI(); confirmDialogSpy.mockResolvedValue(false); const request = createRequestParam(chainId, account.address, true); diff --git a/packages/starknet-snap/src/rpcs/sign-transaction.ts b/packages/starknet-snap/src/rpcs/sign-transaction.ts index ccb61009..24b152e9 100644 --- a/packages/starknet-snap/src/rpcs/sign-transaction.ts +++ b/packages/starknet-snap/src/rpcs/sign-transaction.ts @@ -1,20 +1,15 @@ -import type { DialogResult } from '@metamask/snaps-sdk'; -import type { Call, InvocationsSignerDetails } from 'starknet'; +import type { InvocationsSignerDetails } from 'starknet'; import type { Infer } from 'superstruct'; import { array, object, string, assign, any } from 'superstruct'; +import { renderSignTransactionUI } from '../ui/utils'; import { - confirmDialog, AddressStruct, AuthorizableStruct, BaseRequestStruct, AccountRpcController, CallDataStruct, mapDeprecatedParams, - jsonDataUI, - signerUI, - networkUI, - headerUI, } from '../utils'; import { UserRejectedOpError } from '../utils/exceptions'; import { signTransactions } from '../utils/starknetUtils'; @@ -86,10 +81,12 @@ export class SignTransactionRpc extends AccountRpcController< // Get Starknet expected not to show the confirm dialog, therefore, `enableAuthorize` will set to false to bypass the confirmation // TODO: enableAuthorize should set default to true enableAuthorize && - !(await this.getSignTransactionConsensus( - address, - transactions as unknown as Call[], - )) + !(await renderSignTransactionUI({ + senderAddress: address, + networkName: this.network.name, + chainId: this.network.chainId, + transactions, + })) ) { throw new UserRejectedOpError() as unknown as Error; } @@ -100,26 +97,6 @@ export class SignTransactionRpc extends AccountRpcController< params.transactionsDetail as unknown as InvocationsSignerDetails, )) as SignTransactionResponse; } - - protected async getSignTransactionConsensus( - address: string, - transactions: Call[], - ): Promise { - return await confirmDialog([ - headerUI('Do you want to sign this transaction?'), - signerUI({ - address, - chainId: this.network.chainId, - }), - networkUI({ - networkName: this.network.name, - }), - jsonDataUI({ - label: 'Transaction', - data: transactions, - }), - ]); - } } export const signTransaction = new SignTransactionRpc(); diff --git a/packages/starknet-snap/src/rpcs/switch-network.test.ts b/packages/starknet-snap/src/rpcs/switch-network.test.ts index d01d6d36..cca82729 100644 --- a/packages/starknet-snap/src/rpcs/switch-network.test.ts +++ b/packages/starknet-snap/src/rpcs/switch-network.test.ts @@ -12,12 +12,7 @@ import { InvalidRequestParamsError, UserRejectedOpError, } from '../utils/exceptions'; -import { - buildDividerComponent, - buildNetworkComponent, - buildRowComponent, - prepareConfirmDialog, -} from './__tests__/helper'; +import { prepareRenderSwitchNetworkUI } from './__tests__/helper'; import { switchNetwork } from './switch-network'; import type { SwitchNetworkParams } from './switch-network'; @@ -121,17 +116,15 @@ describe('switchNetwork', () => { currentNetwork, network: requestNetwork, }); - const { confirmDialogSpy } = prepareConfirmDialog(); + const { confirmDialogSpy } = prepareRenderSwitchNetworkUI(); const request = createRequestParam(requestNetwork.chainId, true); await switchNetwork.execute(request); - expect(confirmDialogSpy).toHaveBeenCalledWith([ - { type: 'heading', value: 'Do you want to switch to this network?' }, - buildNetworkComponent(requestNetwork.name), - buildDividerComponent(), - buildRowComponent('Chain ID', requestNetwork.chainId), - ]); + expect(confirmDialogSpy).toHaveBeenCalledWith({ + chainId: requestNetwork.chainId, + name: requestNetwork.name, + }); }); it('throws `UserRejectedRequestError` if user denied the operation', async () => { @@ -141,7 +134,7 @@ describe('switchNetwork', () => { currentNetwork, network: requestNetwork, }); - const { confirmDialogSpy } = prepareConfirmDialog(); + const { confirmDialogSpy } = prepareRenderSwitchNetworkUI(); confirmDialogSpy.mockResolvedValue(false); const request = createRequestParam(requestNetwork.chainId, true); diff --git a/packages/starknet-snap/src/rpcs/switch-network.ts b/packages/starknet-snap/src/rpcs/switch-network.ts index 12ac65b8..ae94048e 100644 --- a/packages/starknet-snap/src/rpcs/switch-network.ts +++ b/packages/starknet-snap/src/rpcs/switch-network.ts @@ -1,18 +1,9 @@ -import type { Component } from '@metamask/snaps-sdk'; import type { Infer } from 'superstruct'; import { assign, boolean } from 'superstruct'; import { NetworkStateManager } from '../state/network-state-manager'; -import { - confirmDialog, - AuthorizableStruct, - BaseRequestStruct, - RpcController, - networkUI, - rowUI, - dividerUI, - headerUI, -} from '../utils'; +import { renderSwitchNetworkUI } from '../ui/utils'; +import { AuthorizableStruct, BaseRequestStruct, RpcController } from '../utils'; import { InvalidNetworkError, UserRejectedOpError } from '../utils/exceptions'; export const SwitchNetworkRequestStruct = assign( @@ -85,7 +76,10 @@ export class SwitchNetworkRpc extends RpcController< // Get Starknet expected show the confirm dialog, while the companion doesnt needed, // therefore, `enableAuthorize` is to enable/disable the confirmation enableAuthorize && - !(await this.getSwitchNetworkConsensus(network.name, network.chainId)) + !(await renderSwitchNetworkUI({ + name: network.name, + chainId: network.chainId, + })) ) { throw new UserRejectedOpError() as unknown as Error; } @@ -95,28 +89,6 @@ export class SwitchNetworkRpc extends RpcController< return true; }); } - - protected async getSwitchNetworkConsensus( - networkName: string, - networkChainId: string, - ) { - const components: Component[] = []; - components.push(headerUI('Do you want to switch to this network?')); - components.push( - networkUI({ - networkName, - }), - ); - components.push(dividerUI()); - components.push( - rowUI({ - label: 'Chain ID', - value: networkChainId, - }), - ); - - return await confirmDialog(components); - } } export const switchNetwork = new SwitchNetworkRpc(); diff --git a/packages/starknet-snap/src/rpcs/watch-asset.test.ts b/packages/starknet-snap/src/rpcs/watch-asset.test.ts index c38bce01..8572b407 100644 --- a/packages/starknet-snap/src/rpcs/watch-asset.test.ts +++ b/packages/starknet-snap/src/rpcs/watch-asset.test.ts @@ -11,13 +11,7 @@ import { InvalidNetworkError, UserRejectedOpError, } from '../utils/exceptions'; -import { - buildAddressComponent, - buildDividerComponent, - buildNetworkComponent, - buildRowComponent, - prepareConfirmDialog, -} from './__tests__/helper'; +import { prepareRenderWatchAssetUI } from './__tests__/helper'; import type { WatchAssetParams } from './watch-asset'; import { watchAsset } from './watch-asset'; @@ -77,7 +71,7 @@ describe('WatchAssetRpc', () => { const request = createRequest({ chainId: network.chainId as unknown as constants.StarknetChainId, }); - const { confirmDialogSpy } = prepareConfirmDialog(); + const { confirmDialogSpy } = prepareRenderWatchAssetUI(); const { getNetworkSpy } = mockNetworkStateManager({ network, }); @@ -108,23 +102,17 @@ describe('WatchAssetRpc', () => { }); await watchAsset.execute(request); - - expect(confirmDialogSpy).toHaveBeenCalledWith([ - { type: 'heading', value: 'Do you want to add this token?' }, - buildNetworkComponent(network.name), - buildDividerComponent(), - buildAddressComponent( - 'Token Address', - request.tokenAddress, - network.chainId, - ), - buildDividerComponent(), - buildRowComponent('Token Name', request.tokenName), - buildDividerComponent(), - buildRowComponent('Token Symbol', request.tokenSymbol), - buildDividerComponent(), - buildRowComponent('Token Decimals', request.tokenDecimals.toString()), - ]); + expect(confirmDialogSpy).toHaveBeenCalledWith({ + chainId: network.chainId, + networkName: network.name, + token: { + address: request.tokenAddress, + chainId: request.chainId, + decimals: request.tokenDecimals, + name: request.tokenName, + symbol: request.tokenSymbol, + }, + }); }); it('throws `InvalidNetworkError` if the network can not be found', async () => { diff --git a/packages/starknet-snap/src/rpcs/watch-asset.ts b/packages/starknet-snap/src/rpcs/watch-asset.ts index 39b364fc..aa825d4e 100644 --- a/packages/starknet-snap/src/rpcs/watch-asset.ts +++ b/packages/starknet-snap/src/rpcs/watch-asset.ts @@ -1,23 +1,17 @@ -import type { Component } from '@metamask/snaps-sdk'; import type { Infer } from 'superstruct'; import { assign, boolean, min, number, object, optional } from 'superstruct'; import { NetworkStateManager } from '../state/network-state-manager'; import { TokenStateManager } from '../state/token-state-manager'; import type { Erc20Token, Network } from '../types/snapState'; +import { renderWatchAssetUI } from '../ui/utils'; import { - confirmDialog, BaseRequestStruct, RpcController, AddressStruct, TokenNameStruct, TokenSymbolStruct, isPreloadedToken, - addressUI, - networkUI, - rowUI, - dividerUI, - headerUI, } from '../utils'; import { DEFAULT_DECIMAL_PLACES } from '../utils/constants'; import { @@ -136,11 +130,11 @@ export class WatchAssetRpc extends RpcController< const network = await this.getNetworkFromChainId(chainId); if ( - !(await this.getWatchAssetConsensus( - network.name, - network.chainId, - erc20Token, - )) + !(await renderWatchAssetUI({ + networkName: network.name, + chainId: network.chainId, + token: erc20Token, + })) ) { throw new UserRejectedOpError() as unknown as Error; } @@ -149,66 +143,6 @@ export class WatchAssetRpc extends RpcController< return true; } - - protected async getWatchAssetConsensus( - networkName: string, - chainId: string, - erc20Token: Erc20Token, - ) { - const { address, name, symbol, decimals } = erc20Token; - - const componentPairs: { - label?: string; - value?: string; - component?: Component; - }[] = [ - { - component: networkUI({ - networkName, - }), - }, - { - component: addressUI({ - label: 'Token Address', - address, - chainId, - }), - }, - { - label: 'Token Name', - value: name, - }, - { - label: 'Token Symbol', - value: symbol, - }, - { - label: 'Token Decimals', - value: decimals.toString(), - }, - ]; - - const components: Component[] = []; - components.push(headerUI('Do you want to add this token?')); - - componentPairs.forEach(({ label, value, component }, idx) => { - if (component) { - components.push(component); - } else if (label && value) { - components.push( - rowUI({ - label, - value, - }), - ); - } - - if (idx < componentPairs.length - 1) { - components.push(dividerUI()); - } - }); - return await confirmDialog(components); - } } export const watchAsset = new WatchAssetRpc(); diff --git a/packages/starknet-snap/src/ui/components/DisplayPrivateKeyUI.tsx b/packages/starknet-snap/src/ui/components/DisplayPrivateKeyUI.tsx new file mode 100644 index 00000000..ca895f08 --- /dev/null +++ b/packages/starknet-snap/src/ui/components/DisplayPrivateKeyUI.tsx @@ -0,0 +1,47 @@ +import type { SnapComponent } from '@metamask/snaps-sdk/jsx'; +import { Box, Icon, Text, Heading, Copyable } from '@metamask/snaps-sdk/jsx'; + +/** + * Builds a UI component to confirm the action of revealing the private key. + * + * @returns A JSX component prompting the user to confirm revealing their private key. + */ +export const DisplayPrivateKeyDialogUI: SnapComponent = () => { + return ( + + Are you sure you want to reveal your private key? + + + + Confirming this action will display your private key. Ensure you are + in a secure environment. + + + + ); +}; + +export type DisplayPrivateKeyAlertUIProps = { + privateKey: string; +}; + +/** + * Builds a UI component to display the private key securely. + * + * @param options - The options to configure the component. + * @param options.privateKey - The private key to be displayed. + * @returns A JSX component for securely displaying the private key with a copyable option. + */ +export const DisplayPrivateKeyAlertUI: SnapComponent< + DisplayPrivateKeyAlertUIProps +> = ({ privateKey }) => { + return ( + + Starknet Account Private Key + + Below is your Starknet Account private key. Keep it confidential. + + + + ); +}; diff --git a/packages/starknet-snap/src/ui/components/SignDeclareTransactionUI.tsx b/packages/starknet-snap/src/ui/components/SignDeclareTransactionUI.tsx new file mode 100644 index 00000000..4765b16c --- /dev/null +++ b/packages/starknet-snap/src/ui/components/SignDeclareTransactionUI.tsx @@ -0,0 +1,41 @@ +import type { SnapComponent } from '@metamask/snaps-sdk/jsx'; +import { Box, Heading, Section } from '@metamask/snaps-sdk/jsx'; +import type { Infer } from 'superstruct'; + +import type { DeclareSignDetailsStruct } from '../../utils'; +import { AddressUI, JsonDataUI, NetworkUI } from '../fragments'; + +export type SignDeclareTransactionUIProps = { + senderAddress: string; + networkName: string; + chainId: string; + declareTransactions: Infer; +}; + +/** + * Builds a UI component for confirming the signing of a Declare transaction. + * + * @param options - The options to configure the component. + * @param options.senderAddress - The address of the sender initiating the transaction. + * @param options.networkName - The name of the blockchain network where the transaction will occur. + * @param options.chainId - The chain ID of the blockchain network. + * @param options.declareTransactions - The details of the Declare transaction. + * @returns A JSX component for the user to review and confirm the Declare transaction signing. + */ +export const SignDeclareTransactionUI: SnapComponent< + SignDeclareTransactionUIProps +> = ({ senderAddress, networkName, chainId, declareTransactions }) => { + return ( + + Do you want to sign this transaction? +
+ + + +
+
+ ); +}; diff --git a/packages/starknet-snap/src/ui/components/SignMessageUI.tsx b/packages/starknet-snap/src/ui/components/SignMessageUI.tsx new file mode 100644 index 00000000..cbde85cc --- /dev/null +++ b/packages/starknet-snap/src/ui/components/SignMessageUI.tsx @@ -0,0 +1,37 @@ +import type { SnapComponent } from '@metamask/snaps-sdk/jsx'; +import { Box, Heading, Section } from '@metamask/snaps-sdk/jsx'; +import type { Infer } from 'superstruct'; + +import type { TypeDataStruct } from '../../utils'; +import { JsonDataUI, SignerUI } from '../fragments'; + +export type SignMessageUIProps = { + address: string; + chainId: string; + typedDataMessage: Infer; +}; + +/** + * Builds a UI component for confirming the signing of a message. + * + * @param options - The options to configure the component. + * @param options.address - The address of the signer. + * @param options.chainId - The chain ID of the blockchain network. + * @param options.typedDataMessage - The typed data message to be signed. + * @returns A JSX component for the user to review and confirm the message signing. + */ +export const SignMessageUI: SnapComponent = ({ + address, + chainId, + typedDataMessage, +}) => { + return ( + + Do you want to sign this message? +
+ + +
+
+ ); +}; diff --git a/packages/starknet-snap/src/ui/components/SignTransactionUI.tsx b/packages/starknet-snap/src/ui/components/SignTransactionUI.tsx new file mode 100644 index 00000000..a2a3f8cc --- /dev/null +++ b/packages/starknet-snap/src/ui/components/SignTransactionUI.tsx @@ -0,0 +1,41 @@ +import type { SnapComponent } from '@metamask/snaps-sdk/jsx'; +import { Box, Heading, Section } from '@metamask/snaps-sdk/jsx'; +import type { Infer } from 'superstruct'; + +import type { CallDataStruct } from '../../utils'; +import { JsonDataUI, AddressUI, NetworkUI } from '../fragments'; + +export type SignTransactionUIProps = { + senderAddress: string; + networkName: string; + chainId: string; + transactions: Infer[]; +}; + +/** + * Builds a UI component for confirming the signing of a transaction. + * + * @param options - The options to configure the component. + * @param options.senderAddress - The address of the sender initiating the transaction. + * @param options.networkName - The name of the blockchain network where the transaction will occur. + * @param options.chainId - The chain ID of the blockchain network. + * @param options.transactions - An array of transactions Call. + * @returns A JSX component for the user to review and confirm the transaction signing. + */ +export const SignTransactionUI: SnapComponent = ({ + senderAddress, + networkName, + chainId, + transactions, +}) => { + return ( + + Do you want to sign this transaction? +
+ + + +
+
+ ); +}; diff --git a/packages/starknet-snap/src/ui/components/SwitchNetworkUI.tsx b/packages/starknet-snap/src/ui/components/SwitchNetworkUI.tsx new file mode 100644 index 00000000..82cf6e4c --- /dev/null +++ b/packages/starknet-snap/src/ui/components/SwitchNetworkUI.tsx @@ -0,0 +1,33 @@ +import type { SnapComponent } from '@metamask/snaps-sdk/jsx'; +import { Box, Heading, Copyable, Bold } from '@metamask/snaps-sdk/jsx'; + +import { NetworkUI } from '../fragments'; + +export type SwitchNetworkUIProps = { + name: string; + chainId: string; +}; + +/** + * Builds a UI component for confirming a network switch. + * + * @param options - The options to configure the component. + * @param options.name - The name of the blockchain network to switch to. + * @param options.chainId - The chain ID of the target blockchain network. + * @returns A JSX component for the user to confirm the network switch. + */ +export const SwitchNetworkUI: SnapComponent = ({ + name, + chainId, +}) => { + return ( + + Do you want to switch to this network? + + + Chain ID + + + + ); +}; diff --git a/packages/starknet-snap/src/ui/components/WatchAssetUI.tsx b/packages/starknet-snap/src/ui/components/WatchAssetUI.tsx new file mode 100644 index 00000000..fad88f7e --- /dev/null +++ b/packages/starknet-snap/src/ui/components/WatchAssetUI.tsx @@ -0,0 +1,62 @@ +import type { SnapComponent } from '@metamask/snaps-sdk/jsx'; +import { + Box, + Divider, + Heading, + Section, + Text, + Row, +} from '@metamask/snaps-sdk/jsx'; + +import type { Erc20Token } from '../../types/snapState'; +import { AddressUI, NetworkUI } from '../fragments'; + +export type WatchAssetUIProps = { + networkName: string; + chainId: string; + token: Erc20Token; +}; + +/** + * Builds a UI component for confirming the addition of an ERC-20 token. + * + * @param options - The options to configure the component. + * @param options.networkName - The name of the blockchain network (e.g., Ethereum, Binance Smart Chain). + * @param options.chainId - The chain ID of the blockchain network. + * @param options.token - The ERC-20 token details, including its name, symbol, address, and decimals. + * @returns A JSX component for the user to confirm adding the token. + */ +export const WatchAssetUI: SnapComponent = ({ + networkName, + chainId, + token, +}) => { + const { name, symbol, address, decimals } = token; + return ( + + Do you want to add this token? + + +
+ +
+ {name ? ( + + {name} + + ) : null} + {symbol ? ( + + {symbol} + + ) : null} + {decimals !== null && ( + + {decimals.toString()} + + )} +
+
+
+ ); +}; diff --git a/packages/starknet-snap/src/ui/utils.tsx b/packages/starknet-snap/src/ui/utils.tsx index 2ee82ae2..0526881d 100644 --- a/packages/starknet-snap/src/ui/utils.tsx +++ b/packages/starknet-snap/src/ui/utils.tsx @@ -1,3 +1,6 @@ +import type { DialogResult } from '@metamask/snaps-sdk'; +import { DialogType } from '@metamask/snaps-sdk'; + import type { FormattedCallData, Network, @@ -10,9 +13,146 @@ import { import { getBalance } from '../utils/starknetUtils'; import type { ExecuteTxnUIErrors } from './components'; import { ExecuteTxnUI } from './components'; +import { + DisplayPrivateKeyAlertUI, + DisplayPrivateKeyDialogUI, +} from './components/DisplayPrivateKeyUI'; +import type { SignDeclareTransactionUIProps } from './components/SignDeclareTransactionUI'; +import { SignDeclareTransactionUI } from './components/SignDeclareTransactionUI'; +import type { SignMessageUIProps } from './components/SignMessageUI'; +import { SignMessageUI } from './components/SignMessageUI'; +import type { SignTransactionUIProps } from './components/SignTransactionUI'; +import { SignTransactionUI } from './components/SignTransactionUI'; +import type { SwitchNetworkUIProps } from './components/SwitchNetworkUI'; +import { SwitchNetworkUI } from './components/SwitchNetworkUI'; +import type { WatchAssetUIProps } from './components/WatchAssetUI'; +import { WatchAssetUI } from './components/WatchAssetUI'; import { LoadingUI } from './fragments/LoadingUI'; import type { TokenTotals } from './types'; +/** + * Renders a confirmation dialog for adding a token to the wallet. + * + * @param props - The properties for the WatchAssetUI component. + * @returns A promise that resolves to the user's decision in the dialog. + */ +export async function renderWatchAssetUI( + props: WatchAssetUIProps, +): Promise { + return await snap.request({ + method: 'snap_dialog', + params: { + type: DialogType.Confirmation, + content: , + }, + }); +} + +/** + * Renders a confirmation dialog for switching to a different network. + * + * @param props - The properties for the SwitchNetworkUI component. + * @returns A promise that resolves to the user's decision in the dialog. + */ +export async function renderSwitchNetworkUI( + props: SwitchNetworkUIProps, +): Promise { + return await snap.request({ + method: 'snap_dialog', + params: { + type: DialogType.Confirmation, + content: , + }, + }); +} + +/** + * Renders a confirmation dialog for signing a transaction. + * + * @param props - The properties for the SignTransactionUI component. + * @returns A promise that resolves to the user's decision in the dialog. + */ +export async function renderSignTransactionUI( + props: SignTransactionUIProps, +): Promise { + return await snap.request({ + method: 'snap_dialog', + params: { + type: DialogType.Confirmation, + content: , + }, + }); +} + +/** + * Renders a confirmation dialog for signing a message. + * + * @param props - The properties for the SignMessageUI component. + * @returns A promise that resolves to the user's decision in the dialog. + */ +export async function renderSignMessageUI( + props: SignMessageUIProps, +): Promise { + return await snap.request({ + method: 'snap_dialog', + params: { + type: DialogType.Confirmation, + content: , + }, + }); +} + +/** + * Renders a confirmation dialog for signing a Declare transaction. + * + * @param props - The properties for the SignDeclareTransactionUI component. + * @returns A promise that resolves to the user's decision in the dialog. + */ +export async function renderSignDeclareTransactionUI( + props: SignDeclareTransactionUIProps, +): Promise { + return await snap.request({ + method: 'snap_dialog', + params: { + type: DialogType.Confirmation, + content: , + }, + }); +} + +/** + * Renders a confirmation dialog asking the user to confirm displaying their private key. + * + * @returns A promise that resolves to the user's decision in the dialog. + */ +export async function renderDisplayPrivateKeyConfirmUI(): Promise { + return await snap.request({ + method: 'snap_dialog', + params: { + type: DialogType.Confirmation, + content: , + }, + }); +} + +/** + * Renders an alert dialog displaying the user's private key securely. + * + * @param privateKey - The private key to display in the alert dialog. + * @returns A promise that resolves when the dialog is dismissed. + */ +export async function renderDisplayPrivateKeyAlertUI( + privateKey: string, +): Promise { + await snap.request({ + method: 'snap_dialog', + params: { + type: DialogType.Alert, + content: , + }, + }); +} + /** * Accumulate the total amount for all tokens involved in calls and fees. *