Skip to content

Commit 26ff5c8

Browse files
[wallet/symbol/mobile] feat: add remaining screens from the beta 2.0 version (#2068)
## Problem The mobile wallet lacked several key user-facing features that are essential for a full-featured Symbol wallet experience (which some of them already implemented in Beta v2.0). Specifically: Missing features: - **Transaction Details** screen. - **Harvesting** screen - delegated harvesting (start/stop, check status, view node info). - **Address Book**. - **Multisig account** support - view and manage multisig accounts. - **Actions** screen - as a central hub for wallet features. - **QR code Scan** screen. - **Assets** screen shows only current account mosaics, but didn't show for other related accounts (such as multisig and bridge). - Can send transfer only from Home screen button. On other screens send button is not very visible. - No receive button Quality: - The **Send** screen was a monolithic component without isolated state hooks, making it difficult to extend and test, and lacked support for pre-filled sender address and multisig flow. - The **Home screen** was missing animated widget transitions. - Several shared components (`AccountDetails`, `ContactDetails`, bridge account screens) lacked unified send/receive entry points, leading to navigation inconsistency. - JS-Doc coverage was inconsistent and there were no lint rules enforcing it. --- ## Solution ### New Screens - **Actions screen** (`src/screens/actions/`) — Central hub screen listing all major wallet feature entry points (external accounts, address book, harvesting, transport, bridge) with illustrated `ActionCard`. - **Address Book screens** (`src/screens/address-book/`) — Full CRUD flow: `ContactList`, `ContactDetails`, `CreateContact`, `EditContact`. Includes `AddressBookWidget` for the Home screen, hooks (`useContactList`, `useContactFormState`, `useAddressBookWidget`), unique name/address validators, contact alert utils, and tab-based filtering. - **Harvesting screen** (`src/screens/harvesting/`) — `Harvesting` screen with `HarvestingForm`, `HarvestingStatus`, `HarvestingSummary` components and hooks (`useHarvestingFormState`, `useHarvestingTransaction`, `useHarvestingAccountInfo`, `useHarvestingSummary`, `useHarvestingWidget`, `useRandomNode`). Supports start/stop delegation, status display, and harvesting info caching. - **Multisig screens** (`src/screens/multisig/`) — `MultisigAccountList`, `MultisigAccountDetails`, `CreateMultisigAccount`, `ModifyMultisigAccount`. Includes `CosignatoryList`, `CosignatureCounter`, `MultisigAccountListItem` components, hooks (`useMultisigAccountList`, `useMultisigTransaction`, `useMultisigTransactionState`, `useCosignatureInput`, `useMultisigWidget`), and `MultisigWidget` for the Home screen. - **TransactionDetails screen** (`src/screens/history/TransactionDetails.jsx`) — Full transaction inspection with `TransactionGraphic`, `AmountBreakdown`, expandable raw fields, cosignature status, safety warnings, and live polling via `useLiveTransactionInfo`. - **Transport / Scan screens** (`src/screens/transport/`) — `Scan` screen (QR camera) and `TransportRequest` screen for reviewing and acting on QR-encoded wallet action requests. Includes `RequestDetails`, `WalletActionGroup`, `WalletActionListItem` components, `useSupportedChains`, and full validation/description/result utils. ### New Shared Components - **`AccountInfoCard`** (`src/components/display/Account/AccountInfoCard.jsx`) — Reusable card showing account name, address, avatar, and balance. Adopted across `AccountDetails`, `ContactDetails`, `BridgeAccountDetails`, and `MultisigAccountDetails` screens, replacing duplicated inline layouts. - **`SendReceiveButtons`** (`src/components/features/SendReceiveButtons.jsx`) — Paired Send/Receive action buttons component added to `AccountDetails`, `ContactDetails`, `TokenDetails`, `BridgeAccountDetails`, and `MultisigAccountDetails`. - **`QrCodeView`** (`src/components/features/QrCodeView.jsx`) — Renders a QR code from a given string value. - **`TabSelector`** (`src/components/controls/TabSelector.jsx`) — Reusable tab bar control (sourced from `wallet/common/symbol`). - **`EmptyListMessage`** (`src/components/feedback/EmptyListMessage.jsx`) — Standardized empty-state message for list screens. - **`AnimatedListItem`** (`src/components/layout/AnimatedListItem.jsx`) — Animated insert/remove wrapper used in `BridgeHistory` and filtered lists. - **`Columns`** (`src/components/layout/Columns.jsx`) — Horizontal column layout primitive. - **`ExpandableCard`** (`src/components/layout/ExpandableCard.jsx`) — Collapsible card for progressive disclosure (used on `TransactionDetails`). - **`StableHeightContainer`** (`src/components/layout/StableHeightContainer.jsx`) — Prevents height jitter on dynamic-height `Alert` in address book forms. - **`MultiColumnList`** (`src/components/layout/MultiColumnList.jsx`) — Multi-column list layout. - **`WidgetContainer`** (`src/components/layout/WidgetContainer.jsx`) — Standardized titled widget wrapper; refactors `AddressBook`, `History`, and `Multisig` widgets. - **`WidgetAnimatedWrapper`** (`src/screens/home/components/WidgetAnimatedWrapper.jsx`) — Animated entrance/exit wrapper for Home screen widgets. - **`ScreenIllustration`** (`src/components/visual/ScreenIllustration.jsx`) — Displays themed screen art images (address book, harvesting, multisig, bridge, etc.). ### Common Library Additions (`wallet/common/symbol`) - **`MultisigModule`** — Full multisig account management: persistent caching, account fetch, aggregate transaction creation for account modification. - **`signTransactionBundle`** / **`announceTransactionBundle`** — Extended to support cosignatures for multisig account modification flows. - **`signTransaction`** — Extended to support cosignatures. - **`isPublicKey`** utility added. - `AccountInfo` now exposes `minApproval` and `minRemoval`. - Aggregate-bonded constants moved to shared constants. - `fetchAccountTransaction` added to wallet controller for Ethereum and Symbol. ### Refactoring & Improvements - **Send screen** — Extracted into `useSendFormState`, `useSendTransaction`, `useSenderInfo` hooks and `send-display` utils; added `senderAddress` route param to support pre-filling sender in multisig flows. - **Home screen** — Pixel-perfect spacing, animated widget list (`WidgetAnimatedWrapper`), `MultisigWidget` integrated. - **`FilteredListScreenTemplate`** — Gained list item insert/remove animation via `AnimatedListItem`. - **`TableView`** — Added `delta` row type; fixed token resolution and incorrect data property handling. - **`InputAddress`** — Added `extraValidators` prop; added `validateAddress` cross-chain validator. - **`Passcode`** — Added verify animation (`usePasscodeJumpAnimation`). - **`ButtonCircle`** — Added size variants. - **`CopyButton`** — Added inverse color property. - **`Divider`** — Added inversed color variant. - **`DialogBox`** — Added disabled state. - **`Alert`** — Added `customIcon` prop. - **Color palette** — Added progress and accent semantic color tokens; expanded primitive palette. - **JS-Doc** — Added ESLint JS-Doc rules (`.eslintrc.yaml`); audited and fixed JS-Doc across hooks, screens, components, and utilities. - **Transaction history** — Local cache for unconfirmed/partial transactions to prevent re-mount animation flashes; `useCosignFlow` for cosign actions from history; more auto-refresh cases. - **App launch** — Reduced startup time by rearranging initial calls in `App.jsx`. - **Asset images** — Added art illustrations, multisig component assets, new icon variants (info-circle, cross-circle, question-circle, lock, message, namespace, arrow, bridge symbol/ethereum). ### Tests - Added tests for: `Actions`, `ContactList`, `ContactDetails`, `CreateContact`, `EditContact`, `Harvesting`, `TransactionDetails`, `MultisigAccountList`, `MultisigAccountDetails`, `CreateMultisigAccount`, `ModifyMultisigAccount`, `Scan`, `TransportRequest`, `transport-actions`. - Added `ContactFixtureBuilder`, `multisig-test-helper`. - Updated existing tests for `AccountDetails`, `AddSeedAccount`, `Assets`, `TokenDetails`, `BridgeAccountDetails`, `BridgeSwap`, `useBridgeHistory`, `History`, `useHistoryData`, `Home`, `TableView`, `TransactionStatusDialog`, `PasscodeManager`, `MultisigModule`, `PersistentStorageRepository`.
1 parent 8d7d7c8 commit 26ff5c8

417 files changed

Lines changed: 19051 additions & 2564 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

wallet/common/core/src/constants/index.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export const TransactionGroup = {
7070
export const REQUIRED_API_METHODS = [
7171
'account.fetchAccountInfo',
7272
'transaction.fetchAccountTransactions',
73+
'transaction.fetchAccountTransaction',
7374
'transaction.fetchTransactionStatus',
7475
'transaction.announceTransaction',
7576
'transaction.announceTransactionBundle',

wallet/common/core/src/index.js

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { ExternalAccountKeystore } from './lib/keystore/ExternalAccountKeystore'
66
import { MnemonicKeystore } from './lib/keystore/MnemonicKeystore';
77
// Storage
88
import { StorageInterface } from './lib/storage/StorageInterface';
9+
import { PersistentStorageRepository } from './lib/storage/PersistentStorageRepository';
910
// Modules
1011
import { AddressBookModule } from './lib/modules/AddressBookModule';
1112
import { BridgeModule } from './lib/modules/BridgeModule';
@@ -19,7 +20,7 @@ import {
1920
relativeToAbsoluteAmount,
2021
safeOperationWithRelativeAmounts
2122
} from './utils/convert';
22-
import { createNetworkMap } from './utils/network';
23+
import { cloneNetworkArrayMap, cloneNetworkObjectMap, createNetworkMap } from './utils/network';
2324
// Constants
2425
import * as constants from './constants';
2526
// Errors
@@ -47,12 +48,15 @@ export {
4748
MnemonicKeystore,
4849
ExternalAccountKeystore,
4950
StorageInterface,
51+
PersistentStorageRepository,
5052
absoluteToRelativeAmount,
5153
relativeToAbsoluteAmount,
5254
safeOperationWithRelativeAmounts,
5355
base32ToHex,
5456
hexToBase32,
5557
createNetworkMap,
58+
cloneNetworkArrayMap,
59+
cloneNetworkObjectMap,
5660
constants,
5761
AddressBookModule,
5862
BridgeModule,

wallet/common/core/src/lib/bridge/BridgeManager.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,6 @@ export class BridgeManager {
278278

279279
/**
280280
* Fetch pending requests that was sent to the bridge.
281-
fetchPendingRequests
282281
* @param {string} mode - 'wrap' or 'unwrap'
283282
* @param {object} searchCriteria - Search criteria.
284283
* @param {number} searchCriteria.pageSize - Number of items to fetch.
@@ -299,9 +298,10 @@ export class BridgeManager {
299298
pageNumber
300299
});
301300
const context = this.#getSwapContext(mode);
302-
const filteredTransactions = transactions.filter(tx => context.source.normalizeAddress(tx.recipientAddress) === bridgeAddress);
301+
const filteredTransactions = transactions.filter(tx =>
302+
tx.recipientAddress
303+
&& context.source.normalizeAddress(tx.recipientAddress) === bridgeAddress);
303304

304-
305305
return filteredTransactions.map(transaction => this.#transactionToPendingRequest(transaction, context));
306306
};
307307

wallet/common/core/src/lib/controller/WalletController.js

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -473,8 +473,11 @@ export class WalletController {
473473

474474
// Select first account
475475
const currentAccount = walletAccounts[this._state.networkIdentifier][0];
476+
const account = await this.selectAccount(currentAccount.publicKey);
476477

477-
return this.selectAccount(currentAccount.publicKey);
478+
this._emit(ControllerEventName.WALLET_CREATE);
479+
480+
return account;
478481
};
479482

480483
/**
@@ -591,7 +594,7 @@ export class WalletController {
591594
if (isExternalAccount) {
592595
// Remove account from the keystore
593596
const keystore = this.#accessKeystore(WalletAccountType.EXTERNAL);
594-
await keystore.removeAccount(networkIdentifier, publicKey, password);
597+
await keystore.removeAccount(publicKey, networkIdentifier, password);
595598
}
596599

597600
// Load existing accounts from persistent storage
@@ -753,6 +756,17 @@ export class WalletController {
753756
return this._api.transaction.fetchTransactionStatus(networkProperties, transactionHash);
754757
};
755758

759+
/**
760+
* Fetch transaction info by transaction hash
761+
* @param {string} transactionHash - transaction hash
762+
* @returns {Promise<Transaction>} - transaction info object
763+
*/
764+
fetchAccountTransaction = async transactionHash => {
765+
const { networkProperties } = this._state;
766+
767+
return this._api.transaction.fetchAccountTransaction(networkProperties, this.currentAccount, transactionHash);
768+
};
769+
756770
/**
757771
* Return wallet mnemonic passphrase from the secure storage
758772
* @returns {Promise<string>} - mnemonic passphrase

wallet/common/core/src/lib/storage/PersistentStorageRepository.js

Lines changed: 61 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,10 @@ export class PersistentStorageRepository {
2424
ADDRESS_BOOK: 'ADDRESS_BOOK',
2525
USER_CURRENCY: 'USER_CURRENCY',
2626
NETWORK_PROPERTIES: 'NETWORK_PROPERTIES',
27-
SELECTED_LANGUAGE: 'SELECTED_LANGUAGE'
27+
SELECTED_LANGUAGE: 'SELECTED_LANGUAGE',
28+
HARVESTING_STATUSES: 'HARVESTING_STATUSES',
29+
HARVESTING_SUMMARIES: 'HARVESTING_SUMMARIES',
30+
MULTISIG_ACCOUNTS: 'MULTISIG_ACCOUNTS'
2831
};
2932

3033
/**
@@ -259,6 +262,44 @@ export class PersistentStorageRepository {
259262
return this.storage.setItem(PersistentStorageRepository.STORAGE_KEYS.NETWORK_PROPERTIES, JSON.stringify(payload));
260263
};
261264

265+
/**
266+
* Get the cached harvesting statuses.
267+
* @returns {Promise<NetworkObjectMap|null>} A promise that resolves to the harvesting statuses object or null if not set.
268+
*/
269+
getHarvestingStatuses = async () => {
270+
const json = await this.storage.getItem(PersistentStorageRepository.STORAGE_KEYS.HARVESTING_STATUSES);
271+
272+
return decodeJson(json);
273+
};
274+
275+
/**
276+
* Set the cached harvesting statuses.
277+
* @param {NetworkObjectMap} payload - The harvesting statuses object to set.
278+
* @returns {Promise<void>} A promise that resolves when the harvesting statuses are set.
279+
*/
280+
setHarvestingStatuses = async payload => {
281+
return this.storage.setItem(PersistentStorageRepository.STORAGE_KEYS.HARVESTING_STATUSES, JSON.stringify(payload));
282+
};
283+
284+
/**
285+
* Get the cached harvesting summaries.
286+
* @returns {Promise<NetworkObjectMap|null>} A promise that resolves to the harvesting summaries object or null if not set.
287+
*/
288+
getHarvestingSummaries = async () => {
289+
const json = await this.storage.getItem(PersistentStorageRepository.STORAGE_KEYS.HARVESTING_SUMMARIES);
290+
291+
return decodeJson(json);
292+
};
293+
294+
/**
295+
* Set the cached harvesting summaries.
296+
* @param {NetworkObjectMap} payload - The harvesting summaries object to set.
297+
* @returns {Promise<void>} A promise that resolves when the harvesting summaries are set.
298+
*/
299+
setHarvestingSummaries = async payload => {
300+
return this.storage.setItem(PersistentStorageRepository.STORAGE_KEYS.HARVESTING_SUMMARIES, JSON.stringify(payload));
301+
};
302+
262303
/**
263304
* Get the selected language.
264305
* @returns {Promise<string|null>} A promise that resolves to the selected language string or null if not set.
@@ -276,6 +317,25 @@ export class PersistentStorageRepository {
276317
return this.storage.setItem(PersistentStorageRepository.STORAGE_KEYS.SELECTED_LANGUAGE, payload);
277318
};
278319

320+
/**
321+
* Get the multisig accounts.
322+
* @returns {Promise<NetworkObjectMap|null>} A promise that resolves to the multisig accounts object or null if not set.
323+
*/
324+
getMultisigAccounts = async () => {
325+
const json = await this.storage.getItem(PersistentStorageRepository.STORAGE_KEYS.MULTISIG_ACCOUNTS);
326+
327+
return decodeJson(json);
328+
};
329+
330+
/**
331+
* Set the multisig accounts.
332+
* @param {NetworkObjectMap} payload - The multisig accounts object to set.
333+
* @returns {Promise<void>} A promise that resolves when the multisig accounts are set.
334+
*/
335+
setMultisigAccounts = async payload => {
336+
return this.storage.setItem(PersistentStorageRepository.STORAGE_KEYS.MULTISIG_ACCOUNTS, JSON.stringify(payload));
337+
};
338+
279339
/**
280340
* Clears all data managed by this repository from storage.
281341
* @returns {Promise<void>} A promise that resolves when all items have been removed.

wallet/common/core/tests/lib/PersistentStorageRepository.test.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,39 @@ const storageTestConfig = [
136136
setterName: 'setNetworkProperties',
137137
setterArguments: [jsonObject],
138138
storagePayload: jsonString
139+
},
140+
{
141+
key: 'MULTISIG_ACCOUNTS',
142+
getterName: 'getMultisigAccounts',
143+
emptyStorageValue: null,
144+
expectedEmptyValue: null,
145+
filledStorageValue: jsonString,
146+
expectedFilledValue: jsonObject,
147+
setterName: 'setMultisigAccounts',
148+
setterArguments: [jsonObject],
149+
storagePayload: jsonString
150+
},
151+
{
152+
key: 'HARVESTING_STATUSES',
153+
getterName: 'getHarvestingStatuses',
154+
emptyStorageValue: null,
155+
expectedEmptyValue: null,
156+
filledStorageValue: jsonString,
157+
expectedFilledValue: jsonObject,
158+
setterName: 'setHarvestingStatuses',
159+
setterArguments: [jsonObject],
160+
storagePayload: jsonString
161+
},
162+
{
163+
key: 'HARVESTING_SUMMARIES',
164+
getterName: 'getHarvestingSummaries',
165+
emptyStorageValue: null,
166+
expectedEmptyValue: null,
167+
filledStorageValue: jsonString,
168+
expectedFilledValue: jsonObject,
169+
setterName: 'setHarvestingSummaries',
170+
setterArguments: [jsonObject],
171+
storagePayload: jsonString
139172
}
140173
];
141174

wallet/common/core/tests/lib/WalletController.test.js

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ const defaultParameters = {
2020
},
2121
transaction: {
2222
fetchAccountTransactions: jest.fn().mockResolvedValue([]),
23+
fetchAccountTransaction: jest.fn().mockResolvedValue(),
2324
fetchTransactionStatus: jest.fn().mockResolvedValue({}),
2425
announceTransaction: jest.fn().mockResolvedValue(),
2526
announceTransactionBundle: jest.fn().mockResolvedValue()
@@ -432,7 +433,7 @@ describe('WalletController', () => {
432433
expect(walletController._state.walletAccounts).toStrictEqual(expectedWalletAccounts);
433434
if (shouldRemoveFromExternalKeystore) {
434435
expect(walletController._keystores.external.removeAccount)
435-
.toHaveBeenCalledWith(accountToRemove.networkIdentifier, accountToRemove.publicKey, password);
436+
.toHaveBeenCalledWith(accountToRemove.publicKey, accountToRemove.networkIdentifier, password);
436437
} else {
437438
expect(walletController._keystores.external.removeAccount)
438439
.not.toHaveBeenCalled();
@@ -658,24 +659,40 @@ describe('WalletController', () => {
658659
controllerMethodName: 'fetchTransactionStatus',
659660
controllerMethodArguments: ['hash-123'],
660661
apiNamespaceName: 'transaction',
661-
apiMethodName: 'fetchTransactionStatus'
662+
apiMethodName: 'fetchTransactionStatus',
663+
createApiMethodArgs: wc => [wc.networkProperties, 'hash-123']
664+
},
665+
{
666+
controllerMethodName: 'fetchAccountTransaction',
667+
controllerMethodArguments: ['hash-123'],
668+
apiNamespaceName: 'transaction',
669+
apiMethodName: 'fetchAccountTransaction',
670+
createApiMethodArgs: wc => [wc.networkProperties, wc.currentAccount, 'hash-123']
662671
},
663672
{
664673
controllerMethodName: 'announceSignedTransaction',
665674
controllerMethodArguments: ['signed-transaction-object', 'group'],
666675
apiNamespaceName: 'transaction',
667-
apiMethodName: 'announceTransaction'
676+
apiMethodName: 'announceTransaction',
677+
createApiMethodArgs: wc => [wc.networkProperties, 'signed-transaction-object', 'group']
668678
},
669679
{
670680
controllerMethodName: 'announceSignedTransactionBundle',
671681
controllerMethodArguments: ['signed-transaction-bundle-object', 'group'],
672682
apiNamespaceName: 'transaction',
673-
apiMethodName: 'announceTransactionBundle'
683+
apiMethodName: 'announceTransactionBundle',
684+
createApiMethodArgs: wc => [wc.networkProperties, 'signed-transaction-bundle-object', 'group']
674685
}
675686
];
676687
const runNetworkApiProxyTests = async config => {
677688
// Arrange:
678-
const { controllerMethodName, controllerMethodArguments, apiNamespaceName, apiMethodName } = config;
689+
const {
690+
controllerMethodName,
691+
controllerMethodArguments,
692+
apiNamespaceName,
693+
apiMethodName,
694+
createApiMethodArgs
695+
} = config;
679696
describe(`${controllerMethodName}()`, () => {
680697
it(`calls api.${apiNamespaceName}.${apiMethodName}() with correct arguments`, async () => {
681698
walletController._state = cloneDeep(filledState);
@@ -689,7 +706,7 @@ describe('WalletController', () => {
689706
// Assert:
690707
expect(result).toStrictEqual(expectedReturnValue);
691708
expect(walletController._api[apiNamespaceName][apiMethodName])
692-
.toHaveBeenCalledWith(walletController._state.networkProperties, ...controllerMethodArguments);
709+
.toHaveBeenCalledWith(...createApiMethodArgs(walletController));
693710
});
694711
});
695712
};

wallet/common/ethereum/src/api/TransactionService.js

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,24 @@ export class TransactionService {
3939
return this.resolveTransactionDTOs(networkProperties, transactionDTOs, account);
4040
};
4141

42+
/**
43+
* Fetches transaction info by hash.
44+
* @param {NetworkProperties} networkProperties - Network properties.
45+
* @param {PublicAccount} currentAccount - Current account.
46+
* @param {string} hash - Requested transaction hash.
47+
* @returns {Promise<Transaction>} - The transaction info.
48+
*/
49+
fetchAccountTransaction = async (networkProperties, currentAccount, transactionHash) => {
50+
const provider = createEthereumJrpcProvider(networkProperties);
51+
52+
const transaction = await provider.getTransaction(transactionHash);
53+
54+
if (!transaction)
55+
throw new ApiError(`Transaction with hash ${transactionHash} not found`);
56+
57+
return this.resolveTransactionDTOs(networkProperties, [transaction], currentAccount);
58+
};
59+
4260
/**
4361
* Fetches the status of a transaction.
4462
* @param {NetworkProperties} networkProperties - Network properties.

wallet/common/ethereum/tests/api/TransactionService.test.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,48 @@ describe('api/TransactionService', () => {
9393
});
9494
});
9595

96+
describe('fetchAccountTransaction', () => {
97+
it('fetches transaction by hash and resolves it', async () => {
98+
// Arrange:
99+
const transactionHash = '0xHASH123';
100+
const transactionDTO = { hash: transactionHash, from: currentAccount.address };
101+
const resolvedTransactions = [{ id: 'tx1' }];
102+
const provider = {
103+
getTransaction: jest.fn().mockResolvedValue(transactionDTO)
104+
};
105+
createEthereumJrpcProviderMock.mockReturnValue(provider);
106+
107+
const service = createService();
108+
const resolveSpy = jest
109+
.spyOn(service, 'resolveTransactionDTOs')
110+
.mockResolvedValue(resolvedTransactions);
111+
112+
// Act:
113+
const result = await service.fetchAccountTransaction(networkProperties, currentAccount, transactionHash);
114+
115+
// Assert:
116+
expect(createEthereumJrpcProviderMock).toHaveBeenCalledWith(networkProperties);
117+
expect(provider.getTransaction).toHaveBeenCalledWith(transactionHash);
118+
expect(resolveSpy).toHaveBeenCalledWith(networkProperties, [transactionDTO], currentAccount);
119+
expect(result).toBe(resolvedTransactions);
120+
});
121+
122+
it('throws ApiError when transaction is not found', async () => {
123+
// Arrange:
124+
const transactionHash = '0xMISSING';
125+
const provider = {
126+
getTransaction: jest.fn().mockResolvedValue(null)
127+
};
128+
createEthereumJrpcProviderMock.mockReturnValue(provider);
129+
130+
const service = createService();
131+
132+
// Act & Assert:
133+
await expect(service.fetchAccountTransaction(networkProperties, currentAccount, transactionHash))
134+
.rejects.toThrow(`Transaction with hash ${transactionHash} not found`);
135+
});
136+
});
137+
96138
describe('announceTransaction', () => {
97139
it('announces transaction and returns hash', async () => {
98140
// Arrange:

wallet/common/symbol/src/api/AccountService.js

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,13 @@ export class AccountService {
7979
namespaces: namespaces.value,
8080
isMultisig: isMultisigRequestSucceeded && multisigInfo.value.cosignatories.length > 0,
8181
cosignatories: isMultisigRequestSucceeded ? multisigInfo.value.cosignatories : [],
82-
multisigAddresses: isMultisigRequestSucceeded ? multisigInfo.value.multisigAddresses : []
82+
multisigAddresses: isMultisigRequestSucceeded ? multisigInfo.value.multisigAddresses : [],
83+
...(isMultisigRequestSucceeded
84+
? {
85+
minApproval: multisigInfo.value.minApproval,
86+
minRemoval: multisigInfo.value.minRemoval
87+
}
88+
: {})
8389
};
8490
};
8591

0 commit comments

Comments
 (0)