mirror of
https://github.com/trezor/trezor-wallet
synced 2024-11-24 09:18:09 +00:00
estimateGasLimit while sending data + feeLevels fix
This commit is contained in:
parent
d4f921ba84
commit
55728f6ba3
@ -120,15 +120,41 @@ const calculateMaxAmount = (balance: string, gasPrice: string, gasLimit: string)
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const getMaxAmount = () => {
|
const setAmountAndTotal = (state: State): State => {
|
||||||
|
|
||||||
|
// if (state.setMax) {
|
||||||
|
// const account: ?Account = findAccount(getState().accounts, accountState.index, accountState.deviceState, accountState.network);
|
||||||
|
// if (!account) return;
|
||||||
|
|
||||||
|
// if (isToken) {
|
||||||
|
// const token: ?Token = findToken(getState().tokens, account.address, state.selectedCurrency, account.deviceState);
|
||||||
|
// if (!token) return;
|
||||||
|
// state.amount = token.balance;
|
||||||
|
// } else {
|
||||||
|
// state.amount = calculateMaxAmount(account.balance, state.gasPrice, state.gasLimit);
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// state.total = calculateTotal(isToken ? '0' : state.amount, state.gasPrice, state.gasLimit);
|
||||||
|
|
||||||
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getFeeLevels = (symbol: string, gasPrice: BigNumber | string, gasLimit: string): Array<FeeLevel> => {
|
export const getFeeLevels = (symbol: string, gasPrice: BigNumber | string, gasLimit: string, selected?: FeeLevel): Array<FeeLevel> => {
|
||||||
const price: BigNumber = typeof gasPrice === 'string' ? new BigNumber(gasPrice) : gasPrice
|
const price: BigNumber = typeof gasPrice === 'string' ? new BigNumber(gasPrice) : gasPrice
|
||||||
const quarter: BigNumber = price.dividedBy(4);
|
const quarter: BigNumber = price.dividedBy(4);
|
||||||
const high: string = price.plus(quarter.times(2)).toString();
|
const high: string = price.plus(quarter.times(2)).toString();
|
||||||
const low: string = price.minus(quarter.times(2)).toString();
|
const low: string = price.minus(quarter.times(2)).toString();
|
||||||
|
|
||||||
|
const customLevel: FeeLevel = selected && selected.value === 'Custom' ? {
|
||||||
|
value: 'Custom',
|
||||||
|
gasPrice: selected.gasPrice,
|
||||||
|
// label: `${ calculateFee(gasPrice, gasLimit) } ${ symbol }`
|
||||||
|
label: `${ calculateFee(selected.gasPrice, gasLimit) } ${ symbol }`
|
||||||
|
} : {
|
||||||
|
value: 'Custom',
|
||||||
|
gasPrice: low,
|
||||||
|
label: ''
|
||||||
|
}
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
@ -146,11 +172,7 @@ export const getFeeLevels = (symbol: string, gasPrice: BigNumber | string, gasLi
|
|||||||
gasPrice: low,
|
gasPrice: low,
|
||||||
label: `${ calculateFee(low, gasLimit) } ${ symbol }`
|
label: `${ calculateFee(low, gasLimit) } ${ symbol }`
|
||||||
},
|
},
|
||||||
{
|
customLevel
|
||||||
value: 'Custom',
|
|
||||||
gasPrice: low,
|
|
||||||
label: '',
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -424,12 +446,10 @@ export const onCurrencyChange = (currency: any): ThunkAction => {
|
|||||||
let gasLimit: string = '';
|
let gasLimit: string = '';
|
||||||
let amount: string = currentState.amount;
|
let amount: string = currentState.amount;
|
||||||
let total: string;
|
let total: string;
|
||||||
console.warn("SEL", currency, currentState.coinSymbol)
|
|
||||||
if (isToken) {
|
if (isToken) {
|
||||||
gasLimit = coin.defaultGasLimitTokens.toString();
|
gasLimit = coin.defaultGasLimitTokens.toString();
|
||||||
if (currentState.setMax) {
|
if (currentState.setMax) {
|
||||||
const token: ?Token = findToken(getState().tokens, account.address, currency.value, accountState.deviceState);
|
const token: ?Token = findToken(getState().tokens, account.address, currency.value, accountState.deviceState);
|
||||||
console.warn("SEL", token, currency.value)
|
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
amount = token.balance;
|
amount = token.balance;
|
||||||
}
|
}
|
||||||
@ -442,7 +462,7 @@ export const onCurrencyChange = (currency: any): ThunkAction => {
|
|||||||
total = calculateTotal(amount, currentState.gasPrice, currentState.gasLimit);
|
total = calculateTotal(amount, currentState.gasPrice, currentState.gasLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
const feeLevels: Array<FeeLevel> = getFeeLevels(currentState.coinSymbol, currentState.gasPrice, gasLimit);
|
const feeLevels: Array<FeeLevel> = getFeeLevels(currentState.coinSymbol, currentState.gasPrice, gasLimit, currentState.selectedFeeLevel);
|
||||||
const selectedFeeLevel: ?FeeLevel = feeLevels.find(f => f.value === currentState.selectedFeeLevel.value);
|
const selectedFeeLevel: ?FeeLevel = feeLevels.find(f => f.value === currentState.selectedFeeLevel.value);
|
||||||
if (!selectedFeeLevel) return;
|
if (!selectedFeeLevel) return;
|
||||||
|
|
||||||
@ -528,7 +548,6 @@ export const onFeeLevelChange = (feeLevel: FeeLevel): ThunkAction => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (feeLevel.value === 'Custom') {
|
if (feeLevel.value === 'Custom') {
|
||||||
// TODO: update value for custom fee
|
|
||||||
state.advanced = true;
|
state.advanced = true;
|
||||||
feeLevel.gasPrice = state.gasPrice;
|
feeLevel.gasPrice = state.gasPrice;
|
||||||
feeLevel.label = `${ calculateFee(state.gasPrice, state.gasLimit) } ${ state.coinSymbol }`;
|
feeLevel.label = `${ calculateFee(state.gasPrice, state.gasLimit) } ${ state.coinSymbol }`;
|
||||||
@ -538,7 +557,6 @@ export const onFeeLevelChange = (feeLevel: FeeLevel): ThunkAction => {
|
|||||||
customLevel.label = '';
|
customLevel.label = '';
|
||||||
state.gasPrice = feeLevel.gasPrice;
|
state.gasPrice = feeLevel.gasPrice;
|
||||||
state.gasLimit = isToken ? coin.defaultGasLimitTokens.toString() : coin.defaultGasLimit.toString();
|
state.gasLimit = isToken ? coin.defaultGasLimitTokens.toString() : coin.defaultGasLimit.toString();
|
||||||
// reset custom gasLimit
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentState.setMax) {
|
if (currentState.setMax) {
|
||||||
@ -563,6 +581,9 @@ export const onFeeLevelChange = (feeLevel: FeeLevel): ThunkAction => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Manually triggered from user
|
||||||
|
// Update gasPrice to recommended value
|
||||||
|
|
||||||
export const updateFeeLevels = (): ThunkAction => {
|
export const updateFeeLevels = (): ThunkAction => {
|
||||||
return (dispatch: Dispatch, getState: GetState): void => {
|
return (dispatch: Dispatch, getState: GetState): void => {
|
||||||
const accountState: ?AccountState = getState().abstractAccount;
|
const accountState: ?AccountState = getState().abstractAccount;
|
||||||
@ -570,7 +591,7 @@ export const updateFeeLevels = (): ThunkAction => {
|
|||||||
const currentState: State = getState().sendForm;
|
const currentState: State = getState().sendForm;
|
||||||
const isToken: boolean = currentState.selectedCurrency !== currentState.coinSymbol;
|
const isToken: boolean = currentState.selectedCurrency !== currentState.coinSymbol;
|
||||||
|
|
||||||
const feeLevels: Array<FeeLevel> = getFeeLevels(currentState.coinSymbol, currentState.recommendedGasPrice, currentState.gasLimit);
|
const feeLevels: Array<FeeLevel> = getFeeLevels(currentState.coinSymbol, currentState.recommendedGasPrice, currentState.gasLimit, currentState.selectedFeeLevel);
|
||||||
const selectedFeeLevel: ?FeeLevel = feeLevels.find(f => f.value === currentState.selectedFeeLevel.value);
|
const selectedFeeLevel: ?FeeLevel = feeLevels.find(f => f.value === currentState.selectedFeeLevel.value);
|
||||||
if (!selectedFeeLevel) return;
|
if (!selectedFeeLevel) return;
|
||||||
|
|
||||||
@ -578,8 +599,7 @@ export const updateFeeLevels = (): ThunkAction => {
|
|||||||
...currentState,
|
...currentState,
|
||||||
feeLevels,
|
feeLevels,
|
||||||
selectedFeeLevel,
|
selectedFeeLevel,
|
||||||
//gasPrice: currentState.recommendedGasPrice, // TODO HERE!
|
gasPrice: selectedFeeLevel.gasPrice,
|
||||||
gasPrice: selectedFeeLevel.gasPrice, // TODO HERE!
|
|
||||||
gasPriceNeedsUpdate: false,
|
gasPriceNeedsUpdate: false,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -642,9 +662,13 @@ export const onGasPriceChange = (gasPrice: string): ThunkAction => {
|
|||||||
state.amount = calculateMaxAmount(account.balance, state.gasPrice, state.gasLimit);
|
state.amount = calculateMaxAmount(account.balance, state.gasPrice, state.gasLimit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
state.total = calculateTotal(isToken ? '0' : state.amount, state.gasPrice, state.gasLimit);
|
||||||
|
} else {
|
||||||
|
// state.gasPrice = currentState.gasPrice;
|
||||||
}
|
}
|
||||||
|
|
||||||
state.total = calculateTotal(isToken ? '0' : state.amount, state.gasPrice, state.gasLimit);
|
|
||||||
|
|
||||||
dispatch({
|
dispatch({
|
||||||
type: SEND.GAS_PRICE_CHANGE,
|
type: SEND.GAS_PRICE_CHANGE,
|
||||||
@ -703,8 +727,8 @@ export const onGasLimitChange = (gasLimit: string): ThunkAction => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const onDataChange = (data: string): ThunkAction => {
|
export const onDataChange = (data: string): AsyncAction => {
|
||||||
return (dispatch: Dispatch, getState: GetState): void => {
|
return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
const currentState: State = getState().sendForm;
|
const currentState: State = getState().sendForm;
|
||||||
const touched = { ...currentState.touched };
|
const touched = { ...currentState.touched };
|
||||||
touched.data = true;
|
touched.data = true;
|
||||||
@ -721,12 +745,44 @@ export const onDataChange = (data: string): ThunkAction => {
|
|||||||
state
|
state
|
||||||
});
|
});
|
||||||
dispatch( validation() );
|
dispatch( validation() );
|
||||||
|
|
||||||
|
dispatch( estimateGasPrice() );
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const estimateGasPrice = (): AsyncAction => {
|
||||||
|
return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
|
|
||||||
|
const accountState: ?AccountState = getState().abstractAccount;
|
||||||
|
if (!accountState) return;
|
||||||
|
const web3instance: ?Web3Instance = getState().web3.filter(w3 => w3.network === accountState.network)[0];
|
||||||
|
if (!web3instance) return;
|
||||||
|
const web3 = web3instance.web3;
|
||||||
|
const currentState: State = getState().sendForm;
|
||||||
|
|
||||||
|
const data: string = '0x' + (currentState.data.length % 2 === 0 ? currentState.data : '0' + currentState.data);
|
||||||
|
const gasLimit = await estimateGas(web3instance.web3, {
|
||||||
|
to: '0xdb6e09ddca62d0959dc4725697e66b8152222aee',
|
||||||
|
data,
|
||||||
|
value: web3.toHex(web3.toWei(currentState.amount, 'ether')),
|
||||||
|
gasPrice: web3.toHex( EthereumjsUnits.convert(currentState.gasPrice, 'gwei', 'wei') ),
|
||||||
|
});
|
||||||
|
|
||||||
|
dispatch({
|
||||||
|
type: SEND.GAS_LIMIT_CHANGE,
|
||||||
|
state: {
|
||||||
|
...currentState,
|
||||||
|
gasLimit: gasLimit.toString()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
export const onSend = (): AsyncAction => {
|
export const onSend = (): AsyncAction => {
|
||||||
//return onSendERC20();
|
//return onSendERC20();
|
||||||
return async (dispatch: Dispatch, getState: GetState): Promise<any> => {
|
return async (dispatch: Dispatch, getState: GetState): Promise<void> => {
|
||||||
|
|
||||||
const accountState: ?AccountState = getState().abstractAccount;
|
const accountState: ?AccountState = getState().abstractAccount;
|
||||||
if (!accountState) return;
|
if (!accountState) return;
|
||||||
@ -741,7 +797,7 @@ export const onSend = (): AsyncAction => {
|
|||||||
|
|
||||||
const address_n = account.addressPath;
|
const address_n = account.addressPath;
|
||||||
|
|
||||||
let data: string = '';
|
let data: string = '0x' + currentState.data;
|
||||||
let txAmount: string = web3.toHex(web3.toWei(currentState.amount, 'ether'));
|
let txAmount: string = web3.toHex(web3.toWei(currentState.amount, 'ether'));
|
||||||
let txAddress: string = currentState.address;
|
let txAddress: string = currentState.address;
|
||||||
if (isToken) {
|
if (isToken) {
|
||||||
|
@ -49,8 +49,6 @@ export const load = (input: string, network: string): AsyncAction => {
|
|||||||
if (!web3instance) return;
|
if (!web3instance) return;
|
||||||
|
|
||||||
const info = await getTokenInfoAsync(web3instance.erc20, input);
|
const info = await getTokenInfoAsync(web3instance.erc20, input);
|
||||||
info.address = input;
|
|
||||||
|
|
||||||
if (info) {
|
if (info) {
|
||||||
return {
|
return {
|
||||||
options: [ info ]
|
options: [ info ]
|
||||||
|
@ -18,13 +18,14 @@ import type {
|
|||||||
Action,
|
Action,
|
||||||
AsyncAction,
|
AsyncAction,
|
||||||
} from '../flowtype';
|
} from '../flowtype';
|
||||||
import type { ContractFactory } from 'web3';
|
import type { ContractFactory, EstimateGasOptions } from 'web3';
|
||||||
|
|
||||||
import type { BigNumber } from 'bignumber.js';
|
import type { BigNumber } from 'bignumber.js';
|
||||||
import type { Account } from '../reducers/AccountsReducer';
|
import type { Account } from '../reducers/AccountsReducer';
|
||||||
import type { PendingTx } from '../reducers/PendingTxReducer';
|
import type { PendingTx } from '../reducers/PendingTxReducer';
|
||||||
import type { Web3Instance } from '../reducers/Web3Reducer';
|
import type { Web3Instance } from '../reducers/Web3Reducer';
|
||||||
import type { Token } from '../reducers/TokensReducer';
|
import type { Token } from '../reducers/TokensReducer';
|
||||||
|
import type { NetworkToken } from '../reducers/LocalStorageReducer';
|
||||||
|
|
||||||
export type Web3Action = {
|
export type Web3Action = {
|
||||||
type: typeof WEB3.READY,
|
type: typeof WEB3.READY,
|
||||||
@ -384,30 +385,39 @@ export const getNonceAsync = (web3: Web3, address: string): Promise<number> => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export const getTokenInfoAsync = (erc20: ContractFactory, address: string): Promise<any> => {
|
export const getTokenInfoAsync = (erc20: ContractFactory, address: string): Promise<?NetworkToken> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
const contract = erc20.at(address, (error, res) => {
|
const contract = erc20.at(address, (error, res) => {
|
||||||
console.warn("callback", error, res)
|
// console.warn("callback", error, res)
|
||||||
});
|
});
|
||||||
|
|
||||||
const info = {};
|
const info: NetworkToken = {
|
||||||
// TODO: handle errors
|
address,
|
||||||
|
name: '',
|
||||||
|
symbol: '',
|
||||||
|
decimals: 0
|
||||||
|
};
|
||||||
|
|
||||||
contract.name.call((error: ?Error, name: ?string) => {
|
contract.name.call((error: ?Error, name: ?string) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
//resolve(null);
|
resolve(null);
|
||||||
//return;
|
return;
|
||||||
|
} else if (name) {
|
||||||
|
info.name = name;
|
||||||
}
|
}
|
||||||
info.name = name;
|
|
||||||
contract.symbol.call((error: ?Error, symbol: ?string) => {
|
contract.symbol.call((error: ?Error, symbol: ?string) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
resolve(null);
|
resolve(null);
|
||||||
return;
|
return;
|
||||||
|
} else if (symbol) {
|
||||||
|
info.symbol = symbol;
|
||||||
}
|
}
|
||||||
info.symbol = symbol;
|
|
||||||
contract.decimals.call((error: ?Error, decimals: ?BigNumber) => {
|
contract.decimals.call((error: ?Error, decimals: ?BigNumber) => {
|
||||||
if (decimals) {
|
if (decimals) {
|
||||||
info.decimals = decimals.toString();
|
info.decimals = decimals.toNumber();
|
||||||
resolve(info);
|
resolve(info);
|
||||||
} else {
|
} else {
|
||||||
resolve(null);
|
resolve(null);
|
||||||
@ -418,25 +428,13 @@ export const getTokenInfoAsync = (erc20: ContractFactory, address: string): Prom
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export const estimateGas = (web3: Web3, gasOptions: any): Promise<any> => {
|
export const estimateGas = (web3: Web3, options: EstimateGasOptions): Promise<number> => {
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
web3.eth.estimateGas(gasOptions, (error, result) => {
|
web3.eth.estimateGas(options, (error: ?Error, gas: ?number) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
reject(error);
|
reject(error);
|
||||||
} else {
|
} else if (typeof gas === 'number'){
|
||||||
resolve(result);
|
resolve(gas);
|
||||||
}
|
|
||||||
});
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
export const getGasPrice2 = (web3: Web3): Promise<any> => {
|
|
||||||
return new Promise((resolve, reject) => {
|
|
||||||
web3.eth.getGasPrice((error, result) => {
|
|
||||||
if (error) {
|
|
||||||
reject(error);
|
|
||||||
} else {
|
|
||||||
resolve(result);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
})
|
})
|
||||||
|
@ -85,6 +85,7 @@ const AdvancedForm = (props: Props) => {
|
|||||||
autoCapitalize="off"
|
autoCapitalize="off"
|
||||||
spellCheck="false"
|
spellCheck="false"
|
||||||
value={ gasLimit }
|
value={ gasLimit }
|
||||||
|
disabled={ coinSymbol === selectedCurrency && data.length > 0 }
|
||||||
onChange={ event => onGasLimitChange(event.target.value) } />
|
onChange={ event => onGasLimitChange(event.target.value) } />
|
||||||
{ errors.gasLimit ? (<span className="error">{ errors.gasLimit }</span>) : null }
|
{ errors.gasLimit ? (<span className="error">{ errors.gasLimit }</span>) : null }
|
||||||
{ warnings.gasLimit ? (<span className="warning">{ warnings.gasLimit }</span>) : null }
|
{ warnings.gasLimit ? (<span className="warning">{ warnings.gasLimit }</span>) : null }
|
||||||
@ -121,8 +122,7 @@ const AdvancedForm = (props: Props) => {
|
|||||||
<span className="what-is-it"></span>
|
<span className="what-is-it"></span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</label>
|
</label>
|
||||||
<textarea value={ data } onChange={ event => onDataChange(event.target.value) }></textarea>
|
<textarea disabled={ coinSymbol !== selectedCurrency } value={ coinSymbol !== selectedCurrency ? '' : data } onChange={ event => onDataChange(event.target.value) }></textarea>
|
||||||
{/* <textarea disabled={ coinSymbol !== selectedCurrency } value={ coinSymbol !== selectedCurrency ? '' : data } onChange={ event => onDataChange(event.target.value) }></textarea> */}
|
|
||||||
{ errors.data ? (<span className="error">{ errors.data }</span>) : null }
|
{ errors.data ? (<span className="error">{ errors.data }</span>) : null }
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
@ -36,6 +36,17 @@ declare module 'web3' {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare export type EstimateGasOptions = {
|
||||||
|
to: string;
|
||||||
|
data: string;
|
||||||
|
value?: string;
|
||||||
|
gasPrice?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
declare export type RawTransaction = {
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
declare class Eth {
|
declare class Eth {
|
||||||
getGasPrice: (callback: (error: Error, gasPrice: string) => void) => void,
|
getGasPrice: (callback: (error: Error, gasPrice: string) => void) => void,
|
||||||
getBalance: (address: string, callback: (error: Error, balance: BigNumber) => void) => void,
|
getBalance: (address: string, callback: (error: Error, balance: BigNumber) => void) => void,
|
||||||
@ -43,10 +54,10 @@ declare module 'web3' {
|
|||||||
getTransaction: (txid: string, callback: (error: Error, result: any) => void) => void,
|
getTransaction: (txid: string, callback: (error: Error, result: any) => void) => void,
|
||||||
getBlockNumber: (callback: (error: Error, blockNumber: number) => void) => void,
|
getBlockNumber: (callback: (error: Error, blockNumber: number) => void) => void,
|
||||||
getBlock: (hash: string, callback: (error: Error, result: any) => void) => void,
|
getBlock: (hash: string, callback: (error: Error, result: any) => void) => void,
|
||||||
getAccounts: (callback: (error: Error, accounts: Array<EthereumAddressT>) => void) => void,
|
// getAccounts: (callback: (error: Error, accounts: Array<EthereumAddressT>) => void) => void,
|
||||||
sign: (payload: string, signer: EthereumAddressT) => Promise<string>,
|
// sign: (payload: string, signer: EthereumAddressT) => Promise<string>,
|
||||||
contract: (abi: Array<Object>) => ContractFactory,
|
contract: (abi: Array<Object>) => ContractFactory,
|
||||||
estimateGas: (options: any, callback: (error: Error, result: any) => void) => void,
|
estimateGas: (options: EstimateGasOptions, callback: (error: ?Error, gas: ?number) => void) => void,
|
||||||
sendRawTransaction: (tx: any, callback: (error: Error, result: any) => void) => void,
|
sendRawTransaction: (tx: any, callback: (error: Error, result: any) => void) => void,
|
||||||
filter: (type: string) => Filter; // return intance with "watch"
|
filter: (type: string) => Filter; // return intance with "watch"
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user