Skip to content

Commit 483157e

Browse files
sethkfmanNicolasMassartmetamaskbottommasini
authored
feat: v7.46.1 (#15550)
## **Description** <!-- Write a short description of the changes included in this pull request, also include relevant motivation and context. Have in mind the following questions: 1. What is the reason for the change? 2. What is the improvement/solution? --> ## **Related issues** Fixes: ## **Manual testing steps** 1. Go to this page... 2. 3. ## **Screenshots/Recordings** <!-- If applicable, add screenshots and/or recordings to visualize the before and after of your change. --> ### **Before** <!-- [screenshots/recordings] --> ### **After** <!-- [screenshots/recordings] --> ## **Pre-merge author checklist** - [ ] I’ve followed [MetaMask Contributor Docs](https://github.com/MetaMask/contributor-docs) and [MetaMask Mobile Coding Standards](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/CODING_GUIDELINES.md). - [ ] I've completed the PR template to the best of my ability - [ ] I’ve included tests if applicable - [ ] I’ve documented my code using [JSDoc](https://jsdoc.app/) format if applicable - [ ] I’ve applied the right labels on the PR (see [labeling guidelines](https://github.com/MetaMask/metamask-mobile/blob/main/.github/guidelines/LABELING_GUIDELINES.md)). Not required for external contributors. ## **Pre-merge reviewer checklist** - [ ] I've manually tested the PR (e.g. pull and build branch, run the app, test code being changed). - [ ] I confirm that this PR addresses all acceptance criteria described in the ticket it closes and includes the necessary testing evidence such as recordings and or screenshots. --------- Co-authored-by: Nico MASSART <NicolasMassart@users.noreply.github.com> Co-authored-by: metamaskbot <metamaskbot@users.noreply.github.com> Co-authored-by: tommasini <46944231+tommasini@users.noreply.github.com>
1 parent ceb964b commit 483157e

8 files changed

Lines changed: 318 additions & 21 deletions

File tree

CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
88
## [Unreleased]
99
- fix(bridge): show "Auto" slippage for Solana swaps
1010

11+
## [7.46.1]
12+
13+
### Fixed
14+
- fix: capture exception with Sentry instead throwing the error ([#15469](https://github.com/MetaMask/metamask-mobile/pull/15469))
15+
1116
## [7.46.0]
1217

1318
### Added
@@ -5405,7 +5410,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
54055410
- [#957](https://github.com/MetaMask/metamask-mobile/pull/957): fix timeouts (#957)
54065411
- [#954](https://github.com/MetaMask/metamask-mobile/pull/954): Bugfix: onboarding navigation (#954)
54075412

5408-
[Unreleased]: https://github.com/MetaMask/metamask-mobile/compare/v7.46.0...HEAD
5413+
[Unreleased]: https://github.com/MetaMask/metamask-mobile/compare/v7.46.1...HEAD
5414+
[7.46.1]: https://github.com/MetaMask/metamask-mobile/compare/v7.46.0...v7.46.1
54095415
[7.46.0]: https://github.com/MetaMask/metamask-mobile/compare/v7.45.1...v7.46.0
54105416
[7.45.1]: https://github.com/MetaMask/metamask-mobile/compare/v7.45.0...v7.45.1
54115417
[7.45.0]: https://github.com/MetaMask/metamask-mobile/compare/v7.44.0...v7.45.0

android/app/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -178,8 +178,8 @@ android {
178178
applicationId "io.metamask"
179179
minSdkVersion rootProject.ext.minSdkVersion
180180
targetSdkVersion rootProject.ext.targetSdkVersion
181-
versionName "7.46.0"
182-
versionCode 1846
181+
versionName "7.46.1"
182+
versionCode 1862
183183
testBuildType System.getProperty('testBuildType', 'debug')
184184
missingDimensionStrategy 'react-native-camera', 'general'
185185
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
Lines changed: 229 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,229 @@
1+
import { getPersistentState } from './getPersistentState';
2+
import { StateMetadata } from '@metamask/base-controller';
3+
4+
describe('getPersistentState', () => {
5+
it('return empty state', () => {
6+
expect(getPersistentState({}, {})).toStrictEqual({});
7+
});
8+
9+
it('return empty state when no properties are persistent', () => {
10+
const persistentState = getPersistentState(
11+
{ count: 1 },
12+
{ count: { anonymous: false, persist: false } },
13+
);
14+
expect(persistentState).toStrictEqual({});
15+
});
16+
17+
it('return persistent state', () => {
18+
const persistentState = getPersistentState(
19+
{
20+
password: 'secret password',
21+
privateKey: '123',
22+
network: 'mainnet',
23+
tokens: ['DAI', 'USDC'],
24+
},
25+
{
26+
password: {
27+
anonymous: false,
28+
persist: true,
29+
},
30+
privateKey: {
31+
anonymous: false,
32+
persist: true,
33+
},
34+
network: {
35+
anonymous: false,
36+
persist: false,
37+
},
38+
tokens: {
39+
anonymous: false,
40+
persist: false,
41+
},
42+
},
43+
);
44+
expect(persistentState).toStrictEqual({
45+
password: 'secret password',
46+
privateKey: '123',
47+
});
48+
});
49+
50+
it('use function to derive persistent state', () => {
51+
const normalizeTransactionHash = (hash: string) => hash.toLowerCase();
52+
53+
const persistentState = getPersistentState(
54+
{
55+
transactionHash: '0X1234',
56+
},
57+
{
58+
transactionHash: {
59+
anonymous: false,
60+
persist: normalizeTransactionHash,
61+
},
62+
},
63+
);
64+
65+
expect(persistentState).toStrictEqual({ transactionHash: '0x1234' });
66+
});
67+
68+
it('allow returning a partial object from a persist function', () => {
69+
const getPersistentTxMeta = (txMeta: { hash: string; value: number }) => ({
70+
value: txMeta.value,
71+
});
72+
73+
const persistentState = getPersistentState(
74+
{
75+
txMeta: {
76+
hash: '0x123',
77+
value: 10,
78+
},
79+
},
80+
{
81+
txMeta: {
82+
anonymous: false,
83+
persist: getPersistentTxMeta,
84+
},
85+
},
86+
);
87+
88+
expect(persistentState).toStrictEqual({ txMeta: { value: 10 } });
89+
});
90+
91+
it('allow returning a nested partial object from a persist function', () => {
92+
const getPersistentTxMeta = (txMeta: {
93+
hash: string;
94+
value: number;
95+
history: { hash: string; value: number }[];
96+
}) => ({
97+
history: txMeta.history.map((entry) => ({ value: entry.value })),
98+
value: txMeta.value,
99+
});
100+
101+
const persistentState = getPersistentState(
102+
{
103+
txMeta: {
104+
hash: '0x123',
105+
history: [
106+
{
107+
hash: '0x123',
108+
value: 9,
109+
},
110+
],
111+
value: 10,
112+
},
113+
},
114+
{
115+
txMeta: {
116+
anonymous: false,
117+
persist: getPersistentTxMeta,
118+
},
119+
},
120+
);
121+
122+
expect(persistentState).toStrictEqual({
123+
txMeta: { history: [{ value: 9 }], value: 10 },
124+
});
125+
});
126+
127+
it('allow transforming types in a persist function', () => {
128+
const persistentState = getPersistentState(
129+
{
130+
count: '1',
131+
},
132+
{
133+
count: {
134+
anonymous: false,
135+
persist: (count) => Number(count),
136+
},
137+
},
138+
);
139+
140+
expect(persistentState).toStrictEqual({ count: 1 });
141+
});
142+
143+
// New test cases for the two key changes
144+
145+
it('exclude state property when no metadata exists for a key', () => {
146+
const state = {
147+
password: 'secret password',
148+
privateKey: '123',
149+
network: 'mainnet',
150+
};
151+
const metadata = {
152+
password: {
153+
anonymous: false,
154+
persist: true,
155+
},
156+
privateKey: {
157+
anonymous: false,
158+
persist: true,
159+
},
160+
} as unknown as StateMetadata<typeof state>;
161+
162+
const persistentState = getPersistentState(state, metadata);
163+
164+
expect(persistentState).toStrictEqual({
165+
password: 'secret password',
166+
privateKey: '123',
167+
});
168+
});
169+
170+
it('exclude state property when an error occurs during derivation', () => {
171+
const state = {
172+
password: 'secret password',
173+
privateKey: '123',
174+
network: 'mainnet',
175+
};
176+
const metadata = {
177+
password: {
178+
anonymous: false,
179+
persist: true,
180+
},
181+
privateKey: {
182+
anonymous: false,
183+
persist: () => {
184+
throw new Error('Derivation error');
185+
},
186+
},
187+
} as unknown as StateMetadata<typeof state>;
188+
189+
const persistentState = getPersistentState(state, metadata);
190+
191+
expect(persistentState).toStrictEqual({
192+
password: 'secret password',
193+
});
194+
});
195+
196+
it('exclude nested objects without metadata', () => {
197+
const state = {
198+
user: {
199+
name: 'John',
200+
settings: {
201+
theme: 'dark',
202+
notifications: true,
203+
},
204+
},
205+
preferences: {
206+
language: 'en',
207+
currency: 'USD',
208+
},
209+
};
210+
const metadata = {
211+
user: {
212+
anonymous: false,
213+
persist: true,
214+
},
215+
} as unknown as StateMetadata<typeof state>;
216+
217+
const persistentState = getPersistentState(state, metadata);
218+
219+
expect(persistentState).toStrictEqual({
220+
user: {
221+
name: 'John',
222+
settings: {
223+
theme: 'dark',
224+
notifications: true,
225+
},
226+
},
227+
});
228+
});
229+
});
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { StateConstraint, StateMetadata } from '@metamask/base-controller';
2+
import { Json } from '@metamask/utils';
3+
import { captureException } from '@sentry/react-native';
4+
5+
/**! IMPORTANT: THIS IS MEANT TO BE TEMPORARY, WE SHOULD NOT USE THIS AND USE THE VERSION IN BASE-CONTROLLER INSTEAD.
6+
* The reason why we are using this now, it's because we have controllers state without metadata.
7+
* An epic will be created to add the metadata to the controllers state without it.
8+
* As an example of a property without metadata: "swapsTransactions" in TransactionController
9+
*/
10+
11+
/**
12+
* Returns the subset of state that should be persisted.
13+
*
14+
* @param state - The controller state.
15+
* @param metadata - The controller state metadata, which describes which pieces of state should be persisted.
16+
* @returns The subset of controller state that should be persisted.
17+
*/
18+
export function getPersistentState<ControllerState extends StateConstraint>(
19+
state: ControllerState,
20+
metadata: StateMetadata<ControllerState>,
21+
): Record<keyof ControllerState, Json> {
22+
return deriveStateFromMetadata(state, metadata, 'persist');
23+
}
24+
25+
/**
26+
* Use the metadata to derive state according to the given metadata property.
27+
*
28+
* @param state - The full controller state.
29+
* @param metadata - The controller metadata.
30+
* @param metadataProperty - The metadata property to use to derive state.
31+
* @returns The metadata-derived controller state.
32+
*/
33+
function deriveStateFromMetadata<ControllerState extends StateConstraint>(
34+
state: ControllerState,
35+
metadata: StateMetadata<ControllerState>,
36+
metadataProperty: 'anonymous' | 'persist',
37+
): Record<keyof ControllerState, Json> {
38+
return (Object.keys(state) as (keyof ControllerState)[]).reduce<
39+
Record<keyof ControllerState, Json>
40+
>((derivedState, key) => {
41+
try {
42+
const stateMetadata = metadata[key];
43+
if (!stateMetadata) {
44+
throw new Error(`No metadata found for '${String(key)}'`);
45+
}
46+
const propertyMetadata = stateMetadata[metadataProperty];
47+
const stateProperty = state[key];
48+
if (typeof propertyMetadata === 'function') {
49+
derivedState[key] = propertyMetadata(stateProperty);
50+
} else if (propertyMetadata) {
51+
derivedState[key] = stateProperty;
52+
}
53+
return derivedState;
54+
} catch (error) {
55+
// This is what change from the original base controller implementation
56+
// This is a temporary solution to capture the error for extraneous data that we did not detect
57+
captureException(error as unknown as Error);
58+
59+
return derivedState;
60+
}
61+
}, {} as never);
62+
}

app/store/persistConfig.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import Device from '../util/device';
99
import { UserState } from '../reducers/user';
1010
import { debounce } from 'lodash';
1111
import Engine, { EngineContext } from '../core/Engine';
12-
import { getPersistentState } from '@metamask/base-controller';
12+
import { getPersistentState } from './getPersistentState/getPersistentState';
1313

1414
const TIMEOUT = 40000;
1515
const STORAGE_DEBOUNCE_DELAY = 200;

bitrise.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2210,16 +2210,16 @@ app:
22102210
PROJECT_LOCATION_IOS: ios
22112211
- opts:
22122212
is_expand: false
2213-
VERSION_NAME: 7.46.0
2213+
VERSION_NAME: 7.46.1
22142214
- opts:
22152215
is_expand: false
2216-
VERSION_NUMBER: 1846
2216+
VERSION_NUMBER: 1862
22172217
- opts:
22182218
is_expand: false
2219-
FLASK_VERSION_NAME: 7.46.0
2219+
FLASK_VERSION_NAME: 7.46.1
22202220
- opts:
22212221
is_expand: false
2222-
FLASK_VERSION_NUMBER: 1846
2222+
FLASK_VERSION_NUMBER: 1862
22232223
- opts:
22242224
is_expand: false
22252225
ANDROID_APK_LINK: ''

0 commit comments

Comments
 (0)