Skip to content

Commit cdaf709

Browse files
committed
Moralis: Enhancement - migrate client requests to go through BWC/BWS
1 parent 00fb9ef commit cdaf709

8 files changed

Lines changed: 436 additions & 741 deletions

File tree

declarations.d.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ declare module '@env' {
2020
export const DOSH_WHITELIST: string;
2121
export const GIT_COMMIT_HASH: string;
2222
export const MIXPANEL_PROJECT_TOKEN: string;
23-
export const MORALIS_API_KEY: string;
2423
export const WALLET_CONNECT_V2_PROJECT_ID: string;
2524
export const ZENLEDGER_CLIENT_ID: string;
2625
export const ZENLEDGER_CLIENT_SECRET: string;

package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -113,7 +113,6 @@
113113
"lodash.uniqby": "4.7.0",
114114
"mixpanel-react-native": "3.3.0",
115115
"moment": "2.29.1",
116-
"moralis": "2.25.2",
117116
"papaparse": "5.3.2",
118117
"path-browserify": "1.0.1",
119118
"react": "19.1.1",

src/store/app/app.effects.spec.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -191,9 +191,6 @@ jest.mock('../location', () => ({
191191
jest.mock('../wallet-connect-v2/wallet-connect-v2.effects', () => ({
192192
walletConnectV2Init: jest.fn(() => ({type: 'WALLET_CONNECT_INIT'})),
193193
}));
194-
jest.mock('../moralis/moralis.effects', () => ({
195-
moralisInit: jest.fn(() => ({type: 'MORALIS_INIT'})),
196-
}));
197194
jest.mock('../external-services/external-services.effects', () => ({
198195
prefetchExternalServicesData: jest.fn(() => ({
199196
type: 'PREFETCH_EXTERNAL_SERVICES',

src/store/app/app.effects.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,6 @@ import {prefetchExternalServicesData} from '../external-services/external-servic
120120
import {receiveCrypto, sendCrypto} from '../wallet/effects/send/send';
121121
import moment from 'moment';
122122
import {FeedbackRateType} from '../../navigation/tabs/settings/about/screens/SendFeedback';
123-
import {moralisInit} from '../moralis/moralis.effects';
124123
import {walletConnectV2Init} from '../wallet-connect-v2/wallet-connect-v2.effects';
125124
import {InAppNotificationMessages} from '../../components/modal/in-app-notification/InAppNotification';
126125
import axios from 'axios';
@@ -291,7 +290,6 @@ export const startAppInit = (): Effect => async (dispatch, getState) => {
291290
if (getState().WALLET_CONNECT_V2?.sessions?.length > 0) {
292291
dispatch(walletConnectV2Init());
293292
}
294-
dispatch(moralisInit());
295293

296294
// Update Coinbase
297295
dispatch(coinbaseInitialize());
Lines changed: 319 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,319 @@
1+
import configureTestStore from '@test/store';
2+
import {
3+
getDecodedTransactionsByHash,
4+
getERC20TokenAllowance,
5+
getERC20TokenBalanceByWallet,
6+
getERC20TokenPrice,
7+
getMultipleEvmTokenPrices,
8+
getMultipleSolanaTokenPrices,
9+
getMultipleTokenPrices,
10+
getSVMTokenBalanceByWallet,
11+
} from './moralis.effects';
12+
import {logManager} from '../../managers/LogManager';
13+
14+
jest.mock('../../managers/LogManager', () => ({
15+
logManager: {
16+
info: jest.fn(),
17+
warn: jest.fn(),
18+
debug: jest.fn(),
19+
error: jest.fn(),
20+
},
21+
}));
22+
23+
const buildMockWallet = () => ({
24+
id: 'wallet-1',
25+
moralisGetTransactionVerbose: jest.fn(),
26+
moralisGetTokenPrice: jest.fn(),
27+
moralisGetMultipleERC20TokenPrices: jest.fn(),
28+
moralisGetMultipleSolTokenPrices: jest.fn(),
29+
moralisGetSolWalletPortfolio: jest.fn(),
30+
moralisGetWalletTokenBalances: jest.fn(),
31+
moralisGetTokenAllowance: jest.fn(),
32+
});
33+
34+
const buildStoreWithWallet = (wallet: ReturnType<typeof buildMockWallet>) =>
35+
configureTestStore({
36+
WALLET: {
37+
keys: {
38+
key1: {isReadOnly: false, wallets: [wallet]},
39+
},
40+
customTokenOptionsByAddress: {},
41+
},
42+
} as any);
43+
44+
describe('moralis.effects (BWS proxy, wallet-signed)', () => {
45+
let wallet: ReturnType<typeof buildMockWallet>;
46+
let store: ReturnType<typeof configureTestStore>;
47+
48+
beforeEach(() => {
49+
wallet = buildMockWallet();
50+
store = buildStoreWithWallet(wallet);
51+
});
52+
53+
describe('getERC20TokenPrice', () => {
54+
it('calls moralisGetTokenPrice with the hex chain', async () => {
55+
const raw = {usdPrice: 123};
56+
wallet.moralisGetTokenPrice.mockResolvedValueOnce(raw);
57+
58+
const result = await store.dispatch(
59+
getERC20TokenPrice({address: '0xabc', chain: 'eth'}),
60+
);
61+
62+
expect(wallet.moralisGetTokenPrice).toHaveBeenCalledWith({
63+
address: '0xabc',
64+
chain: '0x1',
65+
});
66+
expect(result).toEqual(raw);
67+
});
68+
});
69+
70+
describe('getDecodedTransactionsByHash', () => {
71+
it('calls moralisGetTransactionVerbose', async () => {
72+
const raw = {logs: [], to_address_label: 'Contract'};
73+
wallet.moralisGetTransactionVerbose.mockResolvedValueOnce(raw);
74+
75+
const result = await store.dispatch(
76+
getDecodedTransactionsByHash({
77+
transactionHash: '0xhash',
78+
chain: 'matic',
79+
}),
80+
);
81+
82+
expect(wallet.moralisGetTransactionVerbose).toHaveBeenCalledWith({
83+
transactionHash: '0xhash',
84+
chain: '0x89',
85+
});
86+
expect(result).toEqual(raw);
87+
});
88+
});
89+
90+
describe('getMultipleEvmTokenPrices', () => {
91+
it('sends tokens as [{tokenAddress}] (Moralis SDK input shape)', async () => {
92+
wallet.moralisGetMultipleERC20TokenPrices.mockResolvedValueOnce([]);
93+
94+
await getMultipleEvmTokenPrices(wallet as any, ['0xaaa', '0xbbb'], '0x1');
95+
96+
expect(wallet.moralisGetMultipleERC20TokenPrices).toHaveBeenCalledWith({
97+
chain: '0x1',
98+
include: 'percent_change',
99+
tokens: [{tokenAddress: '0xaaa'}, {tokenAddress: '0xbbb'}],
100+
});
101+
});
102+
});
103+
104+
describe('getMultipleSolanaTokenPrices', () => {
105+
it('calls moralisGetMultipleSolTokenPrices', async () => {
106+
wallet.moralisGetMultipleSolTokenPrices.mockResolvedValueOnce([]);
107+
108+
await getMultipleSolanaTokenPrices(wallet as any, ['solAddr1']);
109+
110+
expect(wallet.moralisGetMultipleSolTokenPrices).toHaveBeenCalledWith({
111+
addresses: ['solAddr1'],
112+
network: 'mainnet',
113+
});
114+
});
115+
});
116+
117+
describe('getMultipleTokenPrices', () => {
118+
it('maps EVM prices to the unified shape', async () => {
119+
wallet.moralisGetMultipleERC20TokenPrices.mockResolvedValueOnce([
120+
{tokenAddress: '0xaaa', usdPrice: 2, usdPrice24hrPercentChange: -1.5},
121+
]);
122+
123+
const result = await store.dispatch(
124+
getMultipleTokenPrices({addresses: ['0xaaa'], chain: 'eth'}),
125+
);
126+
127+
expect(result).toEqual([
128+
{tokenAddress: '0xaaa', usdPrice: 2, '24hrPercentChange': -1.5},
129+
]);
130+
});
131+
132+
it('uses the SOL prices endpoint for SVM chains', async () => {
133+
wallet.moralisGetMultipleSolTokenPrices.mockResolvedValueOnce([
134+
{tokenAddress: 'solAddr', usdPrice: 3, usdPrice24hrPercentChange: 1},
135+
]);
136+
137+
const result = await store.dispatch(
138+
getMultipleTokenPrices({addresses: ['solAddr'], chain: 'sol'}),
139+
);
140+
141+
expect(wallet.moralisGetMultipleSolTokenPrices).toHaveBeenCalledWith({
142+
addresses: ['solAddr'],
143+
network: 'mainnet',
144+
});
145+
expect(result).toEqual([
146+
{tokenAddress: 'solAddr', usdPrice: 3, '24hrPercentChange': 1},
147+
]);
148+
});
149+
});
150+
151+
describe('getERC20TokenBalanceByWallet', () => {
152+
it('returns [] without calling BWS for unsupported chains', async () => {
153+
const result = await store.dispatch(
154+
getERC20TokenBalanceByWallet({address: '0xabc', chain: 'btc'}),
155+
);
156+
157+
expect(result).toEqual([]);
158+
expect(wallet.moralisGetWalletTokenBalances).not.toHaveBeenCalled();
159+
});
160+
161+
it('calls moralisGetWalletTokenBalances', async () => {
162+
const raw = [{token_address: '0xaaa', balance: '1'}];
163+
wallet.moralisGetWalletTokenBalances.mockResolvedValueOnce(raw);
164+
165+
const result = await store.dispatch(
166+
getERC20TokenBalanceByWallet({address: '0xabc', chain: 'arb'}),
167+
);
168+
169+
expect(wallet.moralisGetWalletTokenBalances).toHaveBeenCalledWith({
170+
address: '0xabc',
171+
chain: '0xa4b1',
172+
});
173+
expect(result).toEqual(raw);
174+
});
175+
});
176+
177+
describe('getSVMTokenBalanceByWallet', () => {
178+
it('returns the tokens from the moralisGetSolWalletPortfolio response', async () => {
179+
const tokens = [{mint: 'solAddr', amount: '1'}];
180+
wallet.moralisGetSolWalletPortfolio.mockResolvedValueOnce({tokens});
181+
182+
const result = await store.dispatch(
183+
getSVMTokenBalanceByWallet({
184+
address: 'solWallet',
185+
chain: 'sol',
186+
network: 'mainnet',
187+
}),
188+
);
189+
190+
expect(wallet.moralisGetSolWalletPortfolio).toHaveBeenCalledWith({
191+
address: 'solWallet',
192+
network: 'mainnet',
193+
});
194+
expect(result).toEqual(tokens);
195+
});
196+
197+
it('throws for non-SVM chains without calling BWS', async () => {
198+
await expect(
199+
store.dispatch(
200+
getSVMTokenBalanceByWallet({
201+
address: '0xabc',
202+
chain: 'eth',
203+
network: 'mainnet',
204+
}),
205+
),
206+
).rejects.toThrow('Unsupported chain for SVM token balance');
207+
expect(wallet.moralisGetSolWalletPortfolio).not.toHaveBeenCalled();
208+
});
209+
});
210+
211+
describe('getERC20TokenAllowance', () => {
212+
it('sends owner address and hex chain to moralisGetTokenAllowance', async () => {
213+
const raw = {result: []};
214+
wallet.moralisGetTokenAllowance.mockResolvedValueOnce(raw);
215+
216+
const result = await store.dispatch(
217+
getERC20TokenAllowance({chain: 'op', ownerAddress: '0xowner'}),
218+
);
219+
220+
expect(wallet.moralisGetTokenAllowance).toHaveBeenCalledWith({
221+
ownerAddress: '0xowner',
222+
chain: '0xa',
223+
});
224+
expect(result).toEqual(raw);
225+
});
226+
227+
it('includes limit and cursor when provided', async () => {
228+
wallet.moralisGetTokenAllowance.mockResolvedValueOnce({result: []});
229+
230+
await store.dispatch(
231+
getERC20TokenAllowance({
232+
chain: 'eth',
233+
ownerAddress: '0xowner',
234+
limit: 50,
235+
cursor: 'abc',
236+
}),
237+
);
238+
239+
expect(wallet.moralisGetTokenAllowance).toHaveBeenCalledWith({
240+
ownerAddress: '0xowner',
241+
chain: '0x1',
242+
limit: 50,
243+
cursor: 'abc',
244+
});
245+
});
246+
});
247+
248+
describe('getSigningWallet resolution (no wallets yet, e.g. fresh install)', () => {
249+
const emptyStore = () =>
250+
configureTestStore({
251+
WALLET: {keys: {}, customTokenOptionsByAddress: {}},
252+
} as any);
253+
254+
beforeEach(() => {
255+
(logManager.error as jest.Mock).mockClear();
256+
});
257+
258+
it('getMultipleTokenPrices returns [] without logging an error', async () => {
259+
const result = await emptyStore().dispatch(
260+
getMultipleTokenPrices({addresses: ['0xaaa'], chain: 'eth'}),
261+
);
262+
263+
expect(result).toEqual([]);
264+
expect(logManager.error).not.toHaveBeenCalled();
265+
});
266+
267+
it('getSVMTokenBalanceByWallet returns [] without logging an error', async () => {
268+
const result = await emptyStore().dispatch(
269+
getSVMTokenBalanceByWallet({
270+
address: 'solWallet',
271+
chain: 'sol',
272+
network: 'mainnet',
273+
}),
274+
);
275+
276+
expect(result).toEqual([]);
277+
expect(logManager.error).not.toHaveBeenCalled();
278+
});
279+
280+
it('getERC20TokenBalanceByWallet returns [] without logging an error', async () => {
281+
const result = await emptyStore().dispatch(
282+
getERC20TokenBalanceByWallet({address: '0xabc', chain: 'eth'}),
283+
);
284+
285+
expect(result).toEqual([]);
286+
expect(logManager.error).not.toHaveBeenCalled();
287+
});
288+
289+
it('getERC20TokenPrice rejects without logging an error', async () => {
290+
await expect(
291+
emptyStore().dispatch(
292+
getERC20TokenPrice({address: '0xabc', chain: 'eth'}),
293+
),
294+
).rejects.toThrow('No wallet available to sign the Moralis request');
295+
expect(logManager.error).not.toHaveBeenCalled();
296+
});
297+
298+
it('getDecodedTransactionsByHash rejects without logging an error', async () => {
299+
await expect(
300+
emptyStore().dispatch(
301+
getDecodedTransactionsByHash({
302+
transactionHash: '0xhash',
303+
chain: 'eth',
304+
}),
305+
),
306+
).rejects.toThrow('No wallet available to sign the Moralis request');
307+
expect(logManager.error).not.toHaveBeenCalled();
308+
});
309+
310+
it('getERC20TokenAllowance rejects without logging an error', async () => {
311+
await expect(
312+
emptyStore().dispatch(
313+
getERC20TokenAllowance({chain: 'eth', ownerAddress: '0xowner'}),
314+
),
315+
).rejects.toThrow('No wallet available to sign the Moralis request');
316+
expect(logManager.error).not.toHaveBeenCalled();
317+
});
318+
});
319+
});

0 commit comments

Comments
 (0)