Skip to content

Commit

Permalink
Merge pull request #267 from Outblock:266-feature-update-gas-estimate…
Browse files Browse the repository at this point in the history
…-in-storage-check

266-feature-update-gas-estimate-in-storage-check
  • Loading branch information
tombeckenham authored Dec 16, 2024
2 parents 74bbc2a + 5b79d01 commit 7765bc1
Show file tree
Hide file tree
Showing 30 changed files with 1,919 additions and 1,422 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@
"postcss-loader": "^5.3.0",
"postcss-nested": "^5.0.6",
"prettier": "^3.3.3",
"react-devtools": "^4.x.x",
"react-devtools": "^6.0.1",
"react-i18next": "^15.1.1",
"react-iconfont-cli": "^2.0.2",
"shelljs": "^0.8.5",
Expand Down
2,866 changes: 1,643 additions & 1,223 deletions pnpm-lock.yaml

Large diffs are not rendered by default.

36 changes: 17 additions & 19 deletions src/background/controller/wallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,10 @@ import { getAuth } from 'firebase/auth';
import web3, { TransactionError } from 'web3';

import eventBus from '@/eventBus';
import { type FeatureFlags } from '@/shared/types/feature-types';
import { isValidEthereumAddress, withPrefix } from '@/shared/utils/address';
import { getHashAlgo, getSignAlgo } from '@/shared/utils/algo';
// eslint-disable-next-line import/order,no-restricted-imports
// eslint-disable-next-line no-restricted-imports
import { findAddressWithNetwork } from '@/ui/utils/modules/findAddressWithPK';
import {
keyringService,
Expand Down Expand Up @@ -54,7 +55,7 @@ import DisplayKeyring from '../service/keyring/display';
import type { NFTData, NFTModel, StorageInfo, WalletResponse } from '../service/networkModel';
import type { ConnectedSite } from '../service/permission';
import type { Account } from '../service/preference';
import { StorageEvaluator } from '../service/storage-evaluator';
import { type EvaluateStorageResult, StorageEvaluator } from '../service/storage-evaluator';
import type { UserInfoStore } from '../service/user';
import defaultConfig from '../utils/defaultConfig.json';
import { getStoragedAccount } from '../utils/getStoragedAccount';
Expand Down Expand Up @@ -3875,6 +3876,10 @@ export class WalletController extends BaseController {
}
};

getFeatureFlags = async (): Promise<FeatureFlags> => {
return openapiService.getFeatureFlags();
};

allowLilicoPay = async (): Promise<boolean> => {
const isFreeGasFeeKillSwitch = await storage.get('freeGas');
const isFreeGasFeeEnabled = await storage.get('lilicoPayer');
Expand Down Expand Up @@ -4072,24 +4077,17 @@ export class WalletController extends BaseController {
transferAmount?: number; // amount in coins
coin?: string; // coin name
movingBetweenEVMAndFlow?: boolean; // are we moving between EVM and Flow?
} = {}): Promise<{
isStorageSufficient: boolean;
isStorageSufficientAfterAction: boolean;
storageInfo: StorageInfo;
}> => {
} = {}): Promise<EvaluateStorageResult> => {
const address = await this.getCurrentAddress();
const { isStorageSufficient, isStorageSufficientAfterAction, storageInfo } =
await this.storageEvaluator.evaluateStorage(
address!,
transferAmount,
coin,
movingBetweenEVMAndFlow
);
return {
isStorageSufficient,
isStorageSufficientAfterAction,
storageInfo,
};
const isFreeGasFeeEnabled = await this.allowLilicoPay();
const result = await this.storageEvaluator.evaluateStorage(
address!,
transferAmount,
coin,
movingBetweenEVMAndFlow,
isFreeGasFeeEnabled
);
return result;
};

// Tracking stuff
Expand Down
14 changes: 13 additions & 1 deletion src/background/service/conditions-evaluator.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import type { NewsConditionType } from '@/shared/types/news-types';

import { userWalletService } from '../service';
import { StorageEvaluator } from '../service/storage-evaluator';

import type { NewsConditionType } from './networkModel';
import openapi from './openapi';

const CURRENT_VERSION = chrome.runtime.getManifest().version;
Expand All @@ -25,6 +26,11 @@ class ConditionsEvaluator {
if (!currentAddress) return false;
return this.evaluateStorageCondition(currentAddress);
}
case 'insufficientBalance': {
const currentAddress = userWalletService.getCurrentAddress();
if (!currentAddress) return false;
return this.evaluateBalanceCondition(currentAddress);
}

case 'unknown':
default:
Expand Down Expand Up @@ -66,6 +72,12 @@ class ConditionsEvaluator {
const { isStorageSufficient } = await storageEvaluator.evaluateStorage(address);
return !isStorageSufficient;
}

async evaluateBalanceCondition(address: string): Promise<boolean> {
const storageEvaluator = new StorageEvaluator();
const { isBalanceSufficient } = await storageEvaluator.evaluateStorage(address);
return !isBalanceSufficient;
}
}

export default new ConditionsEvaluator();
40 changes: 7 additions & 33 deletions src/background/service/networkModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -357,36 +357,10 @@ export interface DeviceInfo {
device_info: DeviceInfoRequest;
}

/*
* News items
*/
export type NewsType = 'message' | 'image';

export type NewsPriority = 'urgent' | 'high' | 'medium' | 'low';

export type NewsDisplayType =
| 'once' // show once
| 'click' // close it when user click on it
| 'expiry'; // it will display until it expired

export type NewsConditionType =
| 'unknown'
| 'canUpgrade'
| 'isIOS'
| 'isAndroid'
| 'isWeb'
| 'insufficientStorage';

export interface NewsItem {
id: string;
priority: NewsPriority;
type: NewsType;
title: string;
body?: string;
icon?: string;
image?: string;
url?: string;
expiryTime: Date;
displayType: NewsDisplayType;
conditions?: NewsConditionType[];
}
export {
type NewsItem,
type NewsPriority,
type NewsType,
type NewsDisplayType,
type NewsConditionType,
} from '@/shared/types/news-types';
26 changes: 17 additions & 9 deletions src/background/service/openapi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import type { TokenInfo } from 'flow-native-token-registry';
import log from 'loglevel';

import { storage } from '@/background/webapi';
import { type FeatureFlagKey, type FeatureFlags } from '@/shared/types/feature-types';
import { isValidFlowAddress, isValidEthereumAddress } from '@/shared/utils/address';
import { getStringFromHashAlgo, getStringFromSignAlgo } from '@/shared/utils/algo';
import { getPeriodFrequency } from '@/shared/utils/getPeriodFrequency';
Expand Down Expand Up @@ -1332,16 +1333,23 @@ class OpenApiService {
return tokenList.find((item) => item.id === contract_name);
};

getFeatureFlags = async (): Promise<FeatureFlags> => {
try {
const config = await remoteFetch.remoteConfig();
return config.features;
} catch (err) {
console.error(err);
}
// By default, all feature flags are disabled
return {};
};
getFeatureFlag = async (featureFlag: FeatureFlagKey): Promise<boolean> => {
const flags = await this.getFeatureFlags();
return !!flags[featureFlag];
};

getSwapInfo = async (): Promise<boolean> => {
remoteFetch
.remoteConfig()
.then((res) => {
return res.features.swap;
})
.catch((err) => {
console.log('getNFTCollectionInfo -->', err);
});
return false;
return (await this.getFeatureFlags()).swap;
};

// @ts-ignore
Expand Down
61 changes: 40 additions & 21 deletions src/background/service/storage-evaluator.ts
Original file line number Diff line number Diff line change
@@ -1,53 +1,72 @@
import * as fcl from '@onflow/fcl';

import { openapiService } from '../service';

import type { StorageInfo } from './networkModel';
interface EvaluateStorageResult {

export type EvaluateStorageResult = {
isStorageSufficient: boolean;
isBalanceSufficient: boolean;
isStorageSufficientAfterAction: boolean;
storageInfo: StorageInfo;
}
};

export class StorageEvaluator {
private static MINIMUM_FLOW_BALANCE = 0.001;
private static MINIMUM_STORAGE_BUFFER = 10000; // minimum required storage buffer (10,000 bytes)
private static FIXED_MOVE_FEE = 0.001;
private static AVERAGE_TX_FEE = 0.001;
private static AVERAGE_TX_FEE = 0.0005;
private static BYTES_PER_FLOW = 100 * 1024 * 1024; // 100 MB

async evaluateStorage(
address: string,
sendAmount?: number,
coin?: string,
movingBetweenEVMAndFlow?: boolean
movingBetweenEVMAndFlow?: boolean,
freeGas?: boolean
): Promise<EvaluateStorageResult> {
// Get storage info from openapi service
const storageInfo = await openapiService.getStorageInfo(address);

const remainingStorage = storageInfo.capacity - storageInfo.used;
const isStorageSufficient = remainingStorage >= StorageEvaluator.MINIMUM_STORAGE_BUFFER;

let noStorageAfterAction = false;
// Calculate the flow balance that is used by the storage calculation
// I don't "love" this approach as it involves a division, but it
// avoids having to figure out which flow balance is used by the storage calculation
const flowBalanceAffectingStorage = storageInfo.capacity / StorageEvaluator.BYTES_PER_FLOW;

// Check if the flow balance is sufficient
const isBalanceSufficient =
flowBalanceAffectingStorage >= StorageEvaluator.MINIMUM_FLOW_BALANCE;

let isStorageSufficientAfterAction = true;

// Check feature flag
const FEATURE_FLAG_TX_WARNING_PREDICTION =
await openapiService.getFeatureFlag('tx_warning_prediction');

if (isStorageSufficient) {
// Check if there is enough storage after the action
if (sendAmount !== undefined) {
// This is the amount of flow that will be used by the transaction
const flowUsed =
(coin === 'flow' ? sendAmount : 0) +
(movingBetweenEVMAndFlow ? StorageEvaluator.FIXED_MOVE_FEE : 0) +
StorageEvaluator.AVERAGE_TX_FEE;
if (FEATURE_FLAG_TX_WARNING_PREDICTION) {
// The feature is enabled, so we need to check if there is enough storage after the action
if (isStorageSufficient) {
// Check if there is enough storage after the action
if (sendAmount !== undefined) {
// This is the amount of flow that will be used by the transaction
const flowUsed =
(coin === 'flow' ? sendAmount : 0) +
(movingBetweenEVMAndFlow ? StorageEvaluator.FIXED_MOVE_FEE : 0) +
(freeGas ? 0 : StorageEvaluator.AVERAGE_TX_FEE);

const storageAffected = flowUsed * StorageEvaluator.BYTES_PER_FLOW;
const remainingStorageAfterAction = storageInfo.available - storageAffected;
const storageAffected = flowUsed * StorageEvaluator.BYTES_PER_FLOW;
const remainingStorageAfterAction = storageInfo.available - storageAffected;

noStorageAfterAction =
remainingStorageAfterAction < StorageEvaluator.MINIMUM_STORAGE_BUFFER;
isStorageSufficientAfterAction =
remainingStorageAfterAction >= StorageEvaluator.MINIMUM_STORAGE_BUFFER;
}
}
}

return {
isStorageSufficient,
isStorageSufficientAfterAction: !noStorageAfterAction,
isBalanceSufficient,
isStorageSufficientAfterAction,
storageInfo,
};
}
Expand Down
7 changes: 4 additions & 3 deletions src/background/service/userWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import * as fcl from '@onflow/fcl';
import { getApp } from 'firebase/app';
import { getAuth, signInAnonymously } from 'firebase/auth';

import { type ActiveChildType } from '@/shared/types/wallet-types';
import { withPrefix } from '@/shared/utils/address';
import { getHashAlgo, getSignAlgo } from '@/shared/utils/algo';
// eslint-disable-next-line no-restricted-imports
Expand Down Expand Up @@ -30,7 +31,7 @@ interface UserWalletStore {
childAccount: ChildAccount;
network: string;
monitor: string;
activeChild: any;
activeChild: ActiveChildType;
evmEnabled: boolean;
}

Expand Down Expand Up @@ -126,11 +127,11 @@ class UserWallet {
this.store.childAccount = wallet;
};

setActiveWallet = (key: any) => {
setActiveWallet = (key: ActiveChildType) => {
this.store.activeChild = key;
};

getActiveWallet = () => {
getActiveWallet = (): ActiveChildType => {
return this.store.activeChild;
};

Expand Down
8 changes: 8 additions & 0 deletions src/shared/types/feature-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
// Example of features we use, but could be any string key as we add more
export type FeatureFlagKey = 'free_gas' | 'swap' | 'tx_warning_prediction' | string;

// Feature flags
export type FeatureFlags = {
// Other feature flags
[key: FeatureFlagKey]: boolean;
};
34 changes: 34 additions & 0 deletions src/shared/types/news-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
/*
* News items
*/
export type NewsType = 'message' | 'image';

export type NewsPriority = 'urgent' | 'high' | 'medium' | 'low';

export type NewsDisplayType =
| 'once' // show once
| 'click' // close it when user click on it
| 'expiry'; // it will display until it expired

export type NewsConditionType =
| 'unknown'
| 'canUpgrade'
| 'isIOS'
| 'isAndroid'
| 'isWeb'
| 'insufficientStorage'
| 'insufficientBalance';

export interface NewsItem {
id: string;
priority: NewsPriority;
type: NewsType;
title: string;
body?: string;
icon?: string;
image?: string;
url?: string;
expiryTime: Date;
displayType: NewsDisplayType;
conditions?: NewsConditionType[];
}
5 changes: 5 additions & 0 deletions src/shared/types/wallet-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
// Matches exactly 16 hex characters, with optional 0x prefix
export type FlowAddress = `0x${string & { length: 16 }}` | `${string & { length: 16 }}`;

// ActiveChildType is the type of the active child in the wallet. It can be 'evm', a FlowAddress, or null.
export type ActiveChildType = 'evm' | FlowAddress | null;
3 changes: 1 addition & 2 deletions src/ui/FRWComponent/WarningStorageLowSnackbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,6 @@ export const WarningStorageLowSnackbar = ({
return null;
}
return (
// <WarningSnackbar open={true} onClose={() => {}} alertIcon={warningIcon} message={message} />
<></>
<WarningSnackbar open={true} onClose={() => {}} alertIcon={warningIcon} message={message} />
);
};
Loading

0 comments on commit 7765bc1

Please sign in to comment.