Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
b7d981a
[wallet/symbol/mobile] feat: make decimals font size smaller in Amoun…
OlegMakarenko Feb 19, 2026
4443659
[wallet/symbol/mobile] feat: add prop for custom section header in Fi…
OlegMakarenko Feb 19, 2026
94d1113
[wallet/symbol/mobile] feat: add Assets screen for owned tokens prese…
OlegMakarenko Feb 19, 2026
6284949
[wallet/symbol/mobile] feat: add tests for Assets screen
OlegMakarenko Feb 19, 2026
a2cb801
[wallet/symbol/mobile] fix: SelectToken component tests
OlegMakarenko Feb 19, 2026
decdf41
[wallet/symbol/mobile] feat: add Ethereum configuration to known-tokens
OlegMakarenko Feb 19, 2026
b33aa03
[wallet/symbol/mobile] feat: add more global JS-Doc types
OlegMakarenko Feb 27, 2026
a2f7d5e
[wallet/common/core] feat: add network currency JS-Doc type
OlegMakarenko Feb 27, 2026
86f77d8
[wallet/symbol/mobile] feat: add copy button to AccountView in TableView
OlegMakarenko Feb 27, 2026
30a22cd
[wallet/symbol/mobile] feat: add new icons and allow icon variant set…
OlegMakarenko Feb 27, 2026
3546a7f
[wallet/symbol/mobile] fix: remove hardcoded ticker from FeeSelector …
OlegMakarenko Feb 27, 2026
6c39326
[wallet/symbol/mobile] fix: add gap between content and title in Dial…
OlegMakarenko Feb 27, 2026
1225b92
[wallet/symbol/mobile] fix: remove static height in ButtonPlain compo…
OlegMakarenko Feb 27, 2026
862878a
[wallet/symbol/mobile] fix: add shrink style to AccountView component…
OlegMakarenko Feb 27, 2026
dc62203
[wallet/symbol/mobile] feat: add style property to TokenAvatar for be…
OlegMakarenko Feb 27, 2026
aff7cf6
[wallet/symbol/mobile] feat: add StatusRow component
OlegMakarenko Feb 27, 2026
53fdaeb
[wallet/symbol/mobile] feat: add Divider component
OlegMakarenko Feb 27, 2026
838b43e
[wallet/symbol/mobile] fix: add Divider to TransactionConfirmationDia…
OlegMakarenko Feb 27, 2026
7910d91
[wallet/symbol/mobile] feat: add modals property to TransactionScreen…
OlegMakarenko Feb 27, 2026
dfaa86a
[wallet/symbol/mobile] task: update global styles
OlegMakarenko Feb 27, 2026
93a3046
[wallet/symbol/mobile] feat: add getTotalFee amount to calculate tota…
OlegMakarenko Feb 27, 2026
f83ac40
[wallet/symbol/mobile] fix: amount validator
OlegMakarenko Feb 27, 2026
9cda044
[wallet/symbol/mobile] feat: add BridgeSwap and BridgeSwapDetails scr…
OlegMakarenko Feb 27, 2026
b160a98
[wallet/symbol/mobile] fix: bridge wallet controllers network type sy…
OlegMakarenko Feb 27, 2026
5a7fcc0
[wallet/symbol/mobile] feat: add TokenDetails screen, move EpiratonPr…
OlegMakarenko Mar 2, 2026
c79498b
[wallet/symbol/mobile] task: add TokenDetails navigation from Assets …
OlegMakarenko Mar 2, 2026
708ca09
[wallet/symbol/mobile] feat: add BridgeAccountList and BridgeAccountD…
OlegMakarenko Mar 2, 2026
b524dd1
[wallet/symbol/mobile] fix: AccountDetails layout and incorrect Table…
OlegMakarenko Mar 2, 2026
599e65f
[wallet/symbol/mobile] task: add new screens to Router
OlegMakarenko Mar 2, 2026
4ddd0ae
[wallet/symbol/mobile] feat: add localization keys
OlegMakarenko Mar 2, 2026
d4f6296
[wallet/symbol/mobile] feat: add default token selection passed via r…
OlegMakarenko Mar 2, 2026
66b5d07
[wallet/symbol/mobile] feat: add swap button to Home screen
OlegMakarenko Mar 2, 2026
cf2bc1e
[wallet/symbol/mobile] feat: add resolution for symbol.xym token crea…
OlegMakarenko Mar 2, 2026
1622fd1
[wallet/symbol/mobile] fix: JS-Doc and lint
OlegMakarenko Mar 2, 2026
b897793
[wallet/symbol/mobile] feat: add property to disable error message po…
OlegMakarenko Mar 2, 2026
0c9d05a
[wallet/symbol/mobile] fix: error handling on makeRequest helper
OlegMakarenko Mar 2, 2026
9130910
[wallet/common/core] fix: empty confg during data reload, inconsisten…
OlegMakarenko Mar 2, 2026
673e08d
[wallet/symbol/mobile] feat: add tests for useBridge hook
OlegMakarenko Mar 2, 2026
4c88df6
[wallet/symbol/mobile] feat: add tests for useBridgeAccounts hook
OlegMakarenko Mar 2, 2026
dac9ea7
[wallet/symbol/mobile] fix: failing tests
OlegMakarenko Mar 4, 2026
defed22
[wallet/symbol/mobile] feat: add tests for useSwapSelector hook, add …
OlegMakarenko Mar 4, 2026
3866dd9
[wallet/symbol/mobile] feat: add basic tests for useBridgeAmount, use…
OlegMakarenko Mar 4, 2026
aa96a1a
[wallet/symbol/mobile] fix: cleanup tests
OlegMakarenko Mar 4, 2026
0a35e48
[wallet/symbol/mobile] fix: tests console async warning
OlegMakarenko Mar 4, 2026
e75beb3
[wallet/symbol/mobile] feat: add tests for BridgeSwap screen
OlegMakarenko Mar 5, 2026
a8cc195
[wallet/symbol/mobile] fix: improve variables name case in bridge hoo…
OlegMakarenko Mar 6, 2026
e9db4c8
[wallet/symbol/mobile] feat: add tests for BridgeSwapDetails screen
OlegMakarenko Mar 6, 2026
5b8db14
[wallet/symbol/mobile] feat: add tests for BridgeAccountDetails screen
OlegMakarenko Mar 8, 2026
34047a0
[wallet/symbol/mobile] feat: add tests for BridgeAccountList screen
OlegMakarenko Mar 8, 2026
2fe4e1d
[wallet/symbol/mobile] feat: add tests for bridge utils
OlegMakarenko Mar 8, 2026
0ac086c
[wallet/symbol/mobile] feat: add tests for TokenDetails screen
OlegMakarenko Mar 8, 2026
802e95c
[wallet/common/core] fix: failing bridge tests
OlegMakarenko Mar 8, 2026
5ec37aa
[wallet/symbol/mobile] task: post-rebase - install dependencies, fix …
OlegMakarenko Mar 12, 2026
a01ede1
[wallet/symbol/mobile] fix: tests timezone issue on Jenkins
OlegMakarenko Mar 12, 2026
1a8c52d
[wallet/common] task: move address and hash normalization from Bridge…
OlegMakarenko Mar 23, 2026
837f6c8
[wallet/symbol/mobile] task: update TransactionFeeFixtureBuilder Ethe…
OlegMakarenko Mar 23, 2026
0d6afa7
[wallet/symbol/mobile] task: add extra check for token expiration war…
OlegMakarenko Mar 23, 2026
dc5322d
[wallet/symbol/mobile] task: add comments to BridgeSwap test (timers)
OlegMakarenko Mar 23, 2026
724dcef
[wallet/symbol/mobile] fix: lint
OlegMakarenko Mar 23, 2026
c334d2f
[wallet/symbol/mobile] fix: replace loading state with not configured…
OlegMakarenko Mar 24, 2026
2f620ce
[wallet/symbol/mobile] fix: move if statement in useBridgeAmount
OlegMakarenko Mar 24, 2026
71b22e5
[wallet/symbol/mobile] fix: remove empty lines in JS-Doc
OlegMakarenko Mar 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion wallet/common/core/src/constants/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -86,5 +86,7 @@ export const REQUIRED_SDK_METHODS = [
'encryptMessage',
'decryptMessage',
'createPrivateAccount',
'createPrivateKeysFromMnemonic'
'createPrivateKeysFromMnemonic',
'normalizeAddress',
'normalizeTransactionHash'
];
186 changes: 110 additions & 76 deletions wallet/common/core/src/lib/bridge/BridgeManager.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,21 @@ import { absoluteToRelativeAmount, relativeToAbsoluteAmount } from '../../utils/
/** @typedef {import('../../types/Token').Token} Token */
/** @typedef {import('../../types/Token').TokenInfo} TokenInfo */

/**
* @typedef {Object} SwapSideInfo
* @property {string} chainName - The blockchain name.
* @property {TokenInfo} tokenInfo - Token info for this side of the swap.
* @property {function(string): string} normalizeAddress - Function to normalize an address for this chain.
* @property {function(string): string} normalizeTransactionHash - Function to normalize a transaction hash for this chain.
*/

/**
* @typedef {Object} SwapContext
* @property {string} mode - The bridge mode ('wrap' or 'unwrap').
* @property {SwapSideInfo} source - Source side information.
* @property {SwapSideInfo} target - Target side information.
*/


const BridgeMode = {
WRAP: 'wrap',
Expand Down Expand Up @@ -151,15 +166,42 @@ export class BridgeManager {
return controllers.find(c => c.chainName === chainName) || null;
};

/**
* Create a SwapSideInfo object for a given wallet controller.
* @param {WalletControllerWithBridgeModule} walletController - Wallet controller.
* @param {TokenInfo} tokenInfo - Token info.
* @returns {SwapSideInfo} - Swap side information.
*/
#createSwapSideInfo = (walletController, tokenInfo) => ({
chainName: walletController.chainName,
tokenInfo,
normalizeAddress: walletController.walletSdk.normalizeAddress,
normalizeTransactionHash: walletController.walletSdk.normalizeTransactionHash
});

/**
* Get swap context with source and target information based on mode.
* @param {string} mode - 'wrap' or 'unwrap'
* @returns {SwapContext} - Swap context with source and target information.
*/
#getSwapContext = mode => {
const sourceWalletController = this.#getSourceWalletController(mode);
const targetWalletController = this.#getTargetWalletController(mode);
const sourceToken = this.#getSourceToken(mode);
const targetToken = this.#getTargetToken(mode);

return {
mode,
source: this.#createSwapSideInfo(sourceWalletController, sourceToken),
target: this.#createSwapSideInfo(targetWalletController, targetToken)
};
};

/**
* Fetch bridge configuration, source and target token infos, and register tokens in wallet controllers.
* @returns {Promise} - Promise that resolves when loading is complete.
*/
load = async () => {
// Clear config
this.#config = null;
this.#bridgeApi.setNetworkIdentifier(null);

// Get network identifiers from both wallet controllers
const nativeNetworkIdentifier = this.#nativeWalletController.networkIdentifier;
const wrappedNetworkIdentifier = this.#wrappedWalletController.networkIdentifier;
Expand Down Expand Up @@ -197,6 +239,8 @@ export class BridgeManager {
delete config.wrappedNetwork.tokenId;
config.nativeNetwork.tokenInfo = nativeToken;
config.wrappedNetwork.tokenInfo = wrappedToken;
config.nativeNetwork.bridgeAddress = this.#nativeWalletController.walletSdk.normalizeAddress(config.nativeNetwork.bridgeAddress);
config.wrappedNetwork.bridgeAddress = this.#wrappedWalletController.walletSdk.normalizeAddress(config.wrappedNetwork.bridgeAddress);

// Update state
this.#config = config;
Expand All @@ -222,10 +266,10 @@ export class BridgeManager {

// Merge and sort by request transaction timestamp descending
const allData = [...wrapRequests, ...unwrapRequests, ...wrapErrors, ...unwrapErrors, ...wrapPending, ...unwrapPending];
const normalizeHash = hash => hash.startsWith('0x') ? hash.slice(2).toUpperCase() : hash.toUpperCase();

const filteredByRequestHash = allData.filter((request, index, self) =>
index === self.findIndex(item =>
normalizeHash(item.requestTransaction.hash) === normalizeHash(request.requestTransaction.hash)));
item.requestTransaction.hash === request.requestTransaction.hash));
const sortedByTimestamp = filteredByRequestHash.sort((a, b) => b.requestTransaction.timestamp - a.requestTransaction.timestamp);

// Return only the requested number of items
Expand All @@ -242,6 +286,9 @@ export class BridgeManager {
* @returns {Promise<BridgeRequest[]>} - List of requests.
*/
fetchSentRequests = async (mode, { pageSize, pageNumber } = {}) => {
if (!this.#config)
throw new Error('Failed to fetch sent requests. No bridge config fetched');

const walletController = this.#getSourceWalletController(mode);
const bridgeAddress = mode === BridgeMode.WRAP
? this.#config.nativeNetwork.bridgeAddress
Expand All @@ -251,15 +298,9 @@ export class BridgeManager {
pageSize,
pageNumber
});
const lowercaseBridgeAddress = bridgeAddress.toLowerCase();
const filteredTransactions = transactions.filter(tx => tx.recipientAddress?.toLowerCase() === lowercaseBridgeAddress);
const context = {
mode,
sourceToken: this.#getSourceToken(mode),
targetToken: this.#getTargetToken(mode),
sourceChainName: walletController.chainName,
targetChainName: this.#getTargetWalletController(mode).chainName
};
const context = this.#getSwapContext(mode);
const filteredTransactions = transactions.filter(tx => context.source.normalizeAddress(tx.recipientAddress) === bridgeAddress);


return filteredTransactions.map(transaction => this.#transactionToPendingRequest(transaction, context));
};
Expand All @@ -282,13 +323,7 @@ export class BridgeManager {
throw new Error('Failed to fetch bridge requests. No bridge config fetched');

const requestDtos = await this.#bridgeApi.fetchRequests(mode, currentAccount.address, { pageSize, pageNumber });
const context = {
mode,
sourceToken: this.#getSourceToken(mode),
targetToken: this.#getTargetToken(mode),
sourceChainName: this.#getSourceWalletController(mode).chainName,
targetChainName: this.#getTargetWalletController(mode).chainName
};
const context = this.#getSwapContext(mode);

return requestDtos.map(dto => this.#requestFromDto(dto, context));
};
Expand All @@ -311,13 +346,7 @@ export class BridgeManager {
throw new Error('Failed to fetch errors. No bridge config fetched');

const errorDtos = await this.#bridgeApi.fetchErrors(mode, currentAccount.address, { pageSize, pageNumber });
const context = {
mode,
sourceToken: this.#getSourceToken(mode),
targetToken: this.#getTargetToken(mode),
sourceChainName: this.#getSourceWalletController(mode).chainName,
targetChainName: this.#getTargetWalletController(mode).chainName
};
const context = this.#getSwapContext(mode);

return errorDtos.map(dto => this.#errorFromDto(dto, context));
};
Expand Down Expand Up @@ -357,102 +386,107 @@ export class BridgeManager {
}
};

/**
* Convert a transaction to a pending bridge request.
* @param {object} transaction - The transaction object.
* @param {SwapContext} context - Swap context with source and target information.
* @returns {BridgeRequest} - The pending bridge request.
*/
#transactionToPendingRequest(transaction, context) {
const { mode, sourceToken, targetToken, sourceChainName, targetChainName } = context;
const { mode, source, target } = context;

if (!source.tokenInfo || !target.tokenInfo)
throw new Error('Failed to create pending request. Token info is not available');

const transactionTokens = transaction.mosaics ?? transaction.tokens ?? [];

return {
type: mode,
requestStatus: 'confirmed',
sourceChainName,
targetChainName,
sourceTokenInfo: sourceToken,
targetTokenInfo: targetToken,
sourceChainName: source.chainName,
targetChainName: target.chainName,
sourceTokenInfo: source.tokenInfo,
targetTokenInfo: target.tokenInfo,
requestTransaction: {
signerAddress: transaction.senderAddress,
hash: transaction.hash,
signerAddress: source.normalizeAddress(transaction.signerAddress),
hash: source.normalizeTransactionHash(transaction.hash),
height: transaction.height,
timestamp: transaction.timestamp
timestamp: transaction.timestamp,
token: transactionTokens[0] ?? null
}
};
}

/**
* Map request DTO to request object.
* @param {object} dto - Request DTO from the bridge
* @param {object} context - Context information
* @param {string} context.mode - 'wrap' or 'unwrap'
* @param {TokenInfo} context.sourceToken - Source token info
* @param {TokenInfo} context.targetToken - Target token info
* @param {string} context.sourceChainName - Source chain name
* @param {string} context.targetChainName - Target chain name
* @returns {BridgeRequest} - Mapped request object
* @param {object} dto - Request DTO from the bridge.
* @param {SwapContext} context - Swap context with source and target information.
* @returns {BridgeRequest} - Mapped request object.
*/
#requestFromDto(dto, { mode, sourceToken, targetToken, sourceChainName, targetChainName }) {
#requestFromDto(dto, { mode, source, target }) {
if (!source.tokenInfo || !target.tokenInfo)
throw new Error('Failed to map request from DTO. Token info is not available');

const requestTransaction = {
signerAddress: dto.senderAddress,
hash: dto.requestTransactionHash,
signerAddress: source.normalizeAddress(dto.senderAddress),
hash: source.normalizeTransactionHash(dto.requestTransactionHash),
height: dto.requestTransactionHeight ?? null,
timestamp: Math.trunc(dto.requestTimestamp * 1000),
token: {
...sourceToken,
amount: absoluteToRelativeAmount(dto.requestAmount, sourceToken.divisibility)
...source.tokenInfo,
amount: absoluteToRelativeAmount(dto.requestAmount, source.tokenInfo.divisibility)
}
};
const payoutTransaction = dto.payoutTransactionHash ? {
recipientAddress: dto.destinationAddress,
hash: dto.payoutTransactionHash,
recipientAddress: target.normalizeAddress(dto.destinationAddress),
hash: target.normalizeTransactionHash(dto.payoutTransactionHash),
height: dto.payoutTransactionHeight ?? null,
timestamp: Math.trunc(dto.payoutTimestamp * 1000),
token: {
...targetToken,
amount: absoluteToRelativeAmount(dto.payoutNetAmount, targetToken.divisibility)
...target.tokenInfo,
amount: absoluteToRelativeAmount(dto.payoutNetAmount, target.tokenInfo.divisibility)
}
} : null;

return {
type: mode,
sourceChainName,
targetChainName,
sourceTokenInfo: sourceToken,
targetTokenInfo: targetToken,
sourceChainName: source.chainName,
targetChainName: target.chainName,
sourceTokenInfo: source.tokenInfo,
targetTokenInfo: target.tokenInfo,
payoutStatus: dto.payoutStatus,
payoutConversionRate: dto.payoutConversionRate
? absoluteToRelativeAmount(dto.payoutConversionRate, targetToken.divisibility)
? absoluteToRelativeAmount(dto.payoutConversionRate, target.tokenInfo.divisibility)
: null,
payoutTotalFee: dto.payoutTotalFee
? absoluteToRelativeAmount(dto.payoutTotalFee, targetToken.divisibility)
? absoluteToRelativeAmount(dto.payoutTotalFee, target.tokenInfo.divisibility)
: null,
requestTransaction,
payoutTransaction
};
}

/**
* Private: Map error DTO to error object.
* @param {object} dto - Error DTO from the bridge
* @param {object} context - Context information
* @param {string} context.mode - 'wrap' or 'unwrap'
* @param {TokenInfo} context.sourceToken - Source token info
* @param {TokenInfo} context.targetToken - Target token info
* @param {string} context.sourceChainName - Source chain name
* @param {string} context.targetChainName - Target chain name
* @returns {BridgeError} - Mapped error object
* Map error DTO to error object.
* @param {object} dto - Error DTO from the bridge.
* @param {SwapContext} context - Swap context with source and target information.
* @returns {BridgeError} - Mapped error object.
*/
#errorFromDto(dto, { mode, sourceToken, targetToken, sourceChainName, targetChainName }) {
#errorFromDto(dto, { mode, source, target }) {
const requestTransaction = {
signerAddress: dto.senderAddress,
hash: dto.requestTransactionHash,
signerAddress: source.normalizeAddress(dto.senderAddress),
hash: source.normalizeTransactionHash(dto.requestTransactionHash),
height: dto.requestTransactionHeight ?? null,
timestamp: Math.trunc(dto.requestTimestamp * 1000)
};

return {
type: mode,
requestStatus: 'error',
sourceChainName,
targetChainName,
sourceTokenInfo: sourceToken,
targetTokenInfo: targetToken,
sourceChainName: source.chainName,
targetChainName: target.chainName,
sourceTokenInfo: source.tokenInfo,
targetTokenInfo: target.tokenInfo,
errorMessage: dto.errorMessage,
requestTransaction
};
Expand Down
12 changes: 12 additions & 0 deletions wallet/common/core/src/lib/controller/WalletController.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ export class WalletController {
/** @type {ProtocolNetworkApi} */
_api;

/** @type {ProtocolWalletSdk} */
_sdk;

/** @type {NetworkManager} */
_networkManager;

Expand Down Expand Up @@ -124,6 +127,7 @@ export class WalletController {
this.#chainName = chainName;
this.#ticker = ticker;
this._api = api;
this._sdk = sdk;
this.networkIdentifiers = networkIdentifiers;
this.#createDefaultNetworkProperties = createDefaultNetworkProperties;
this.#setStateProcessor = setStateProcessor;
Expand Down Expand Up @@ -186,6 +190,14 @@ export class WalletController {
return this._api;
}

/**
* Returns the wallet SDK instance.
* @returns {ProtocolWalletSdk} - The wallet SDK instance.
*/
get walletSdk() {
return this._sdk;
}

/**
* Returns the ticker symbol of the main network currency.
* @returns {string} - The ticker symbol of the main network currency.
Expand Down
8 changes: 8 additions & 0 deletions wallet/common/core/src/types/Network.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,19 @@
* A map where the key is a network identifier and the value is another object/map with keys of type K and values of type V.
*/

/**
* @typedef {Object} NetworkCurrency
* @property {string} id - Token identifier.
* @property {number} divisibility - Token divisibility.
* @property {string} name - Token name or symbol.
*/

/**
* @typedef {Object} NetworkProperties
* @property {string} nodeUrl - API node URL.
* @property {string} networkIdentifier - Network identifier.
* @property {number} chainHeight - Chain height at the time of the request.
* @property {NetworkCurrency} networkCurrency - The native currency information for the network.
*/

export default {};
Loading