From 6fb71d1ec46e0ec03123dd2c8f35ffeac7a31fac Mon Sep 17 00:00:00 2001 From: fernandomg Date: Wed, 27 May 2020 08:47:44 -0300 Subject: [PATCH] chore: add types Not sure right now how to properly deal with `d.ts` files. So, I decided to let those types that depend on others inside a module. --- src/logic/contracts/methodIds.ts | 26 ++++- src/logic/tokens/store/model/token.ts | 16 ++- src/logic/tokens/utils/tokenHelpers.ts | 17 +++- .../loadOutgoingTransactions.ts | 7 +- .../transactions/utils/transactionHelpers.ts | 98 ++++++++++++------- src/routes/safe/store/models/confirmation.ts | 3 +- src/routes/safe/store/models/transaction.ts | 80 +++------------ .../safe/store/models/types/confirmation.d.ts | 10 ++ .../safe/store/models/types/transaction.ts | 71 ++++++++++++++ 9 files changed, 205 insertions(+), 123 deletions(-) create mode 100644 src/routes/safe/store/models/types/confirmation.d.ts create mode 100644 src/routes/safe/store/models/types/transaction.ts diff --git a/src/logic/contracts/methodIds.ts b/src/logic/contracts/methodIds.ts index 1137e2d7..351df109 100644 --- a/src/logic/contracts/methodIds.ts +++ b/src/logic/contracts/methodIds.ts @@ -52,8 +52,26 @@ const METHOD_TO_ID = { '0x694e80c3': SAFE_METHODS_NAMES.CHANGE_THRESHOLD, } -export const decodeParamsFromSafeMethod = (data) => { - const [methodId, params] = [data.slice(0, 10), data.slice(10)] +type SafeMethods = typeof SAFE_METHODS_NAMES[keyof typeof SAFE_METHODS_NAMES] +type TokenMethods = 'transfer' | 'transferFrom' | 'safeTransferFrom' + +type DecodedValues = Array<{ + name: string + value: string +}> + +type SafeDecodedParams = { + [key in SafeMethods]?: DecodedValues +} + +type TokenDecodedParams = { + [key in TokenMethods]?: DecodedValues +} + +export type DecodedMethods = SafeDecodedParams | TokenDecodedParams | null + +export const decodeParamsFromSafeMethod = (data: string): SafeDecodedParams | null => { + const [methodId, params] = [data.slice(0, 10) as keyof typeof METHOD_TO_ID | string, data.slice(10)] switch (methodId) { // swapOwner @@ -104,11 +122,11 @@ export const decodeParamsFromSafeMethod = (data) => { } } -const isSafeMethod = (methodId: string) => { +const isSafeMethod = (methodId: string): boolean => { return !!METHOD_TO_ID[methodId] } -export const decodeMethods = (data: string) => { +export const decodeMethods = (data: string): DecodedMethods => { const [methodId, params] = [data.slice(0, 10), data.slice(10)] if (isSafeMethod(methodId)) { diff --git a/src/logic/tokens/store/model/token.ts b/src/logic/tokens/store/model/token.ts index 1034fed5..fc072be2 100644 --- a/src/logic/tokens/store/model/token.ts +++ b/src/logic/tokens/store/model/token.ts @@ -1,6 +1,15 @@ -import { Record } from 'immutable' +import { Record, RecordOf } from 'immutable' -export const makeToken = Record({ +export type TokenProps = { + address: string + name: string + symbol: string + decimals: number | string + logoUri?: string | null + balance?: number | string +} + +export const makeToken = Record({ address: '', name: '', symbol: '', @@ -8,5 +17,6 @@ export const makeToken = Record({ logoUri: '', balance: undefined, }) - // balance is only set in extendedSafeTokensSelector when we display user's token balances + +export type Token = RecordOf diff --git a/src/logic/tokens/utils/tokenHelpers.ts b/src/logic/tokens/utils/tokenHelpers.ts index b2c3f215..3e94fb19 100644 --- a/src/logic/tokens/utils/tokenHelpers.ts +++ b/src/logic/tokens/utils/tokenHelpers.ts @@ -1,15 +1,16 @@ import logo from 'src/assets/icons/icon_etherTokens.svg' import generateBatchRequests from 'src/logic/contracts/generateBatchRequests' import { getStandardTokenContract, getTokenInfos } from 'src/logic/tokens/store/actions/fetchTokens' -import { makeToken } from 'src/logic/tokens/store/model/token' +import { makeToken, Token } from 'src/logic/tokens/store/model/token' import { ALTERNATIVE_TOKEN_ABI } from 'src/logic/tokens/utils/alternativeAbi' import { web3ReadOnly as web3 } from 'src/logic/wallets/getWeb3' import { isEmptyData } from 'src/routes/safe/store/actions/transactions/utils/transactionHelpers' +import { TxServiceModel } from 'src/routes/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions' export const ETH_ADDRESS = '0x000' export const SAFE_TRANSFER_FROM_WITHOUT_DATA_HASH = '42842e0e' -export const getEthAsToken = (balance: string) => { +export const getEthAsToken = (balance: string): Token => { return makeToken({ address: ETH_ADDRESS, name: 'Ether', @@ -20,7 +21,7 @@ export const getEthAsToken = (balance: string) => { }) } -export const isAddressAToken = async (tokenAddress) => { +export const isAddressAToken = async (tokenAddress): Promise => { // SECOND APPROACH: // They both seem to work the same // const tokenContract = await getStandardTokenContract() @@ -45,7 +46,9 @@ export const isSendERC721Transaction = (tx: any, txCode: string, knownTokens: an ) } -export const getERC20DecimalsAndSymbol = async (tokenAddress: string): Promise => { +export const getERC20DecimalsAndSymbol = async ( + tokenAddress: string, +): Promise<{ decimals: number; symbol: string }> => { const tokenInfos = await getTokenInfos(tokenAddress) if (tokenInfos === null) { @@ -61,7 +64,11 @@ export const getERC20DecimalsAndSymbol = async (tokenAddress: string): Promise { +export const isSendERC20Transaction = async ( + tx: TxServiceModel, + txCode: string, + knownTokens: any, +): Promise => { let isSendTokenTx = !isSendERC721Transaction(tx, txCode, knownTokens) && isTokenTransfer(tx) if (isSendTokenTx) { diff --git a/src/routes/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions.ts b/src/routes/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions.ts index 34d0214d..9665c071 100644 --- a/src/routes/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions.ts +++ b/src/routes/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions.ts @@ -8,6 +8,7 @@ import FetchTransactions from 'src/routes/safe/store/actions/transactions/fetchT import { buildTx, isCancelTransaction } from 'src/routes/safe/store/actions/transactions/utils/transactionHelpers' import { SAFE_REDUCER_ID } from 'src/routes/safe/store/reducer/safe' import { store } from 'src/store' +import { DecodedMethods } from 'src/logic/contracts/methodIds' export type ConfirmationServiceModel = { owner: string @@ -16,17 +17,13 @@ export type ConfirmationServiceModel = { transactionHash: string } -export type DecodedData = { - [key: string]: Array<{ [key: string]: string | number }> -} - export type TxServiceModel = { baseGas: number blockNumber?: number | null confirmations: ConfirmationServiceModel[] creationTx?: boolean | null data?: string | null - dataDecoded?: DecodedData | null + dataDecoded?: DecodedMethods executionDate?: string | null executor: string gasPrice: number diff --git a/src/routes/safe/store/actions/transactions/utils/transactionHelpers.ts b/src/routes/safe/store/actions/transactions/utils/transactionHelpers.ts index 9b415471..b3965995 100644 --- a/src/routes/safe/store/actions/transactions/utils/transactionHelpers.ts +++ b/src/routes/safe/store/actions/transactions/utils/transactionHelpers.ts @@ -1,48 +1,57 @@ import { List, Map } from 'immutable' -import { decodeMethods } from 'src/logic/contracts/methodIds' +import { DecodedMethods, decodeMethods } from 'src/logic/contracts/methodIds' import { TOKEN_REDUCER_ID } from 'src/logic/tokens/store/reducer/tokens' import { getERC20DecimalsAndSymbol, isSendERC20Transaction, isSendERC721Transaction, } from 'src/logic/tokens/utils/tokenHelpers' -import { ZERO_ADDRESS, sameAddress } from 'src/logic/wallets/ethAddresses' +import { sameAddress, ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses' import { EMPTY_DATA } from 'src/logic/wallets/ethTransactions' import { makeConfirmation } from 'src/routes/safe/store/models/confirmation' +import { Confirmation } from 'src/routes/safe/store/models/types/confirmation' import { makeTransaction } from 'src/routes/safe/store/models/transaction' +import { + Transaction, + TransactionStatus, + TransactionStatusValues, + TransactionTypes, + TransactionTypeValues, +} from 'src/routes/safe/store/models/types/transaction' import { CANCELLATION_TRANSACTIONS_REDUCER_ID } from 'src/routes/safe/store/reducer/cancellationTransactions' import { SAFE_REDUCER_ID } from 'src/routes/safe/store/reducer/safe' import { TRANSACTIONS_REDUCER_ID } from 'src/routes/safe/store/reducer/transactions' import { store } from 'src/store' import { safeSelector, safeTransactionsSelector } from 'src/routes/safe/store/selectors' import { addOrUpdateTransactions } from 'src/routes/safe/store/actions/transactions/addOrUpdateTransactions' +import { TxServiceModel } from 'src/routes/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions' -export const isEmptyData = (data?: string | null) => { +export const isEmptyData = (data?: string | null): boolean => { return !data || data === EMPTY_DATA } -export const isInnerTransaction = (tx: any, safeAddress: string): boolean => { +export const isInnerTransaction = (tx: TxServiceModel, safeAddress: string): boolean => { return sameAddress(tx.to, safeAddress) && Number(tx.value) === 0 } -export const isCancelTransaction = (tx: any, safeAddress: string): boolean => { +export const isCancelTransaction = (tx: TxServiceModel, safeAddress: string): boolean => { return isInnerTransaction(tx, safeAddress) && isEmptyData(tx.data) } -export const isPendingTransaction = (tx: any, cancelTx: any): boolean => { +export const isPendingTransaction = (tx: Transaction, cancelTx: Transaction): boolean => { return (!!cancelTx && cancelTx.status === 'pending') || tx.status === 'pending' } -export const isModifySettingsTransaction = (tx: any, safeAddress: string): boolean => { +export const isModifySettingsTransaction = (tx: TxServiceModel, safeAddress: string): boolean => { return isInnerTransaction(tx, safeAddress) && !isEmptyData(tx.data) } -export const isMultiSendTransaction = (tx: any): boolean => { +export const isMultiSendTransaction = (tx: TxServiceModel): boolean => { return !isEmptyData(tx.data) && tx.data.substring(0, 10) === '0x8d80ff0a' && Number(tx.value) === 0 } -export const isUpgradeTransaction = (tx: any): boolean => { +export const isUpgradeTransaction = (tx: TxServiceModel): boolean => { return ( !isEmptyData(tx.data) && isMultiSendTransaction(tx) && @@ -51,11 +60,16 @@ export const isUpgradeTransaction = (tx: any): boolean => { ) } -export const isOutgoingTransaction = (tx: any, safeAddress: string): boolean => { +export const isOutgoingTransaction = (tx: TxServiceModel, safeAddress: string): boolean => { return !sameAddress(tx.to, safeAddress) && !isEmptyData(tx.data) } -export const isCustomTransaction = async (tx: any, txCode: string, safeAddress: string, knownTokens: any) => { +export const isCustomTransaction = async ( + tx: TxServiceModel, + txCode: string, + safeAddress: string, + knownTokens: any, +): Promise => { return ( isOutgoingTransaction(tx, safeAddress) && !(await isSendERC20Transaction(tx, txCode, knownTokens)) && @@ -96,7 +110,7 @@ export const getRefundParams = async ( return refundParams } -export const getDecodedParams = (tx: any): any => { +export const getDecodedParams = (tx: TxServiceModel): DecodedMethods => { if (tx.dataDecoded) { return Object.keys(tx.dataDecoded).reduce((acc, key) => { acc[key] = { @@ -114,9 +128,9 @@ export const getDecodedParams = (tx: any): any => { return null } -export const getConfirmations = (tx: any): List => { +export const getConfirmations = (tx: TxServiceModel): List => { return List( - tx.confirmations.map((conf: any) => + tx.confirmations.map((conf) => makeConfirmation({ owner: conf.owner, hash: conf.transactionHash, @@ -126,7 +140,11 @@ export const getConfirmations = (tx: any): List => { ) } -export const isTransactionCancelled = (tx: any, outgoingTxs: Array, cancellationTxs: { number: any }): boolean => { +export const isTransactionCancelled = ( + tx: TxServiceModel, + outgoingTxs: Array, + cancellationTxs: { number: TxServiceModel }, +): boolean => { return ( // not executed !tx.isExecuted && @@ -137,49 +155,56 @@ export const isTransactionCancelled = (tx: any, outgoingTxs: Array, cancell ) } -export const calculateTransactionStatus = (tx: any, { owners, threshold }: any, currentUser?: string | null): any => { +export const calculateTransactionStatus = ( + tx: Transaction, + { owners, threshold }: any, + currentUser?: string | null, +): TransactionStatusValues => { let txStatus if (tx.isExecuted && tx.isSuccessful) { - txStatus = 'success' + txStatus = TransactionStatus.SUCCESS } else if (tx.cancelled) { - txStatus = 'cancelled' + txStatus = TransactionStatus.CANCELLED } else if (tx.confirmations.size === threshold) { - txStatus = 'awaiting_execution' + txStatus = TransactionStatus.AWAITING_EXECUTION } else if (tx.creationTx) { - txStatus = 'success' + txStatus = TransactionStatus.SUCCESS } else if (!tx.confirmations.size || !!tx.isPending) { - txStatus = 'pending' + txStatus = TransactionStatus.PENDING } else { const userConfirmed = tx.confirmations.filter((conf) => conf.owner === currentUser).size === 1 const userIsSafeOwner = owners.filter((owner) => owner.address === currentUser).size === 1 - txStatus = !userConfirmed && userIsSafeOwner ? 'awaiting_your_confirmation' : 'awaiting_confirmations' + txStatus = + !userConfirmed && userIsSafeOwner + ? TransactionStatus.AWAITING_YOUR_CONFIRMATION + : TransactionStatus.AWAITING_CONFIRMATIONS } if (tx.isSuccessful === false) { - txStatus = 'failed' + txStatus = TransactionStatus.FAILED } return txStatus } -export const calculateTransactionType = (tx: any): string => { - let txType = 'outgoing' +export const calculateTransactionType = (tx: Transaction): TransactionTypeValues => { + let txType = TransactionTypes.OUTGOING if (tx.isTokenTransfer) { - txType = 'token' + txType = TransactionTypes.TOKEN } else if (tx.isCollectibleTransfer) { - txType = 'collectible' + txType = TransactionTypes.COLLECTIBLE } else if (tx.modifySettingsTx) { - txType = 'settings' + txType = TransactionTypes.SETTINGS } else if (tx.isCancellationTx) { - txType = 'cancellation' + txType = TransactionTypes.CANCELLATION } else if (tx.customTx) { - txType = 'custom' + txType = TransactionTypes.CUSTOM } else if (tx.creationTx) { - txType = 'creation' + txType = TransactionTypes.CREATION } else if (tx.upgradeTx) { - txType = 'upgrade' + txType = TransactionTypes.UPGRADE } return txType @@ -193,7 +218,7 @@ export const buildTx = async ({ safe, tx, txCode, -}): Promise => { +}): Promise => { const safeAddress = safe.address const isModifySettingsTx = isModifySettingsTransaction(tx, safeAddress) const isTxCancelled = isTransactionCancelled(tx, outgoingTxs, cancellationTxs) @@ -208,7 +233,7 @@ export const buildTx = async ({ const confirmations = getConfirmations(tx) const { decimals = null, symbol = null } = isSendERC20Tx ? await getERC20DecimalsAndSymbol(tx.to) : {} - const txToStore = makeTransaction({ + const txToStore: Transaction = makeTransaction({ baseGas: tx.baseGas, blockNumber: tx.blockNumber, cancelled: isTxCancelled, @@ -252,7 +277,7 @@ export const buildTx = async ({ export const mockTransaction = (tx, safeAddress: string, state): Promise => { const submissionDate = new Date().toISOString() - const transactionStructure: any = { + const transactionStructure: TxServiceModel = { blockNumber: null, confirmationsRequired: null, dataDecoded: decodeMethods(tx.data), @@ -290,9 +315,10 @@ export const mockTransaction = (tx, safeAddress: string, state): Promise => }) } -export const updateStoredTransactionsStatus = (dispatch, walletRecord) => { +export const updateStoredTransactionsStatus = (dispatch, walletRecord): void => { const state = store.getState() const safe = safeSelector(state) + if (safe) { const safeAddress = safe.address const transactions = safeTransactionsSelector(state) diff --git a/src/routes/safe/store/models/confirmation.ts b/src/routes/safe/store/models/confirmation.ts index 6e66a003..81b63552 100644 --- a/src/routes/safe/store/models/confirmation.ts +++ b/src/routes/safe/store/models/confirmation.ts @@ -1,6 +1,7 @@ import { Record } from 'immutable' +import { ConfirmationProps } from './types/confirmation' -export const makeConfirmation = Record({ +export const makeConfirmation = Record({ owner: '', type: 'initialised', hash: '', diff --git a/src/routes/safe/store/models/transaction.ts b/src/routes/safe/store/models/transaction.ts index 0968937d..07a0e178 100644 --- a/src/routes/safe/store/models/transaction.ts +++ b/src/routes/safe/store/models/transaction.ts @@ -1,70 +1,14 @@ import { List, Map, Record } from 'immutable' import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses' +import { + TransactionProps, + PendingActionType, + TransactionStatus, + TransactionTypes, +} from 'src/routes/safe/store/models/types/transaction' -export type TransactionType = - | 'incoming' - | 'outgoing' - | 'settings' - | 'custom' - | 'creation' - | 'cancellation' - | 'upgrade' - | 'token' - | 'collectible' - -export type TransactionStatus = - | 'awaiting_your_confirmation' - | 'awaiting_confirmations' - | 'success' - | 'failed' - | 'cancelled' - | 'awaiting_execution' - | 'pending' - -export type PendingActionType = 'Confirm' | 'Reject' - -export type TransactionProps = { - baseGas: number - blockNumber?: number | null - cancelled?: boolean - confirmations: List - creationTx: boolean - customTx: boolean - data?: string | null - decimals?: (number | string) | null - decodedParams: any - executionDate?: string | null - executionTxHash?: string | null - executor: string - gasPrice: number - gasToken: string - isCancellationTx: boolean - isCollectibleTransfer: boolean - isExecuted: boolean - isPending?: boolean - isSuccessful: boolean - isTokenTransfer: boolean - modifySettingsTx: boolean - multiSendTx: boolean - nonce?: number | null - operation: number - origin: string | null - ownersWithPendingActions: Map> - recipient: string - refundParams: any - refundReceiver: string - safeTxGas: number - safeTxHash: string - status?: TransactionStatus - submissionDate?: string | null - symbol?: string | null - type: TransactionType - upgradeTx: boolean - value: string -} - -export const makeTransaction = Record({ +export const makeTransaction = Record({ baseGas: 0, blockNumber: 0, cancelled: false, @@ -89,18 +33,16 @@ export const makeTransaction = Record({ nonce: 0, operation: 0, origin: null, - ownersWithPendingActions: Map({ confirm: List([]), reject: List([]) }), + ownersWithPendingActions: Map({ [PendingActionType.CONFIRM]: List([]), [PendingActionType.REJECT]: List([]) }), recipient: '', refundParams: null, refundReceiver: ZERO_ADDRESS, safeTxGas: 0, safeTxHash: '', - status: 'awaiting', + status: TransactionStatus.PENDING, submissionDate: '', symbol: '', - type: 'outgoing', + type: TransactionTypes.OUTGOING, upgradeTx: false, - value: 0, + value: '0', }) - -export type Transaction = Record diff --git a/src/routes/safe/store/models/types/confirmation.d.ts b/src/routes/safe/store/models/types/confirmation.d.ts new file mode 100644 index 00000000..65e879c9 --- /dev/null +++ b/src/routes/safe/store/models/types/confirmation.d.ts @@ -0,0 +1,10 @@ +import { RecordOf } from 'immutable' + +export type ConfirmationProps = { + owner: string + type: string + hash: string + signature: string | null +} + +export type Confirmation = RecordOf diff --git a/src/routes/safe/store/models/types/transaction.ts b/src/routes/safe/store/models/types/transaction.ts new file mode 100644 index 00000000..ea4e12e3 --- /dev/null +++ b/src/routes/safe/store/models/types/transaction.ts @@ -0,0 +1,71 @@ +export enum TransactionTypes { + INCOMING = 'incoming', + OUTGOING = 'outgoing', + SETTINGS = 'settings', + CUSTOM = 'custom', + CREATION = 'creation', + CANCELLATION = 'cancellation', + UPGRADE = 'upgrade', + TOKEN = 'token', + COLLECTIBLE = 'collectible', +} +export type TransactionTypeValues = typeof TransactionTypes[keyof typeof TransactionTypes] + +export enum TransactionStatus { + AWAITING_YOUR_CONFIRMATION = 'awaiting_your_confirmation', + AWAITING_CONFIRMATIONS = 'awaiting_confirmations', + SUCCESS = 'success', + FAILED = 'failed', + CANCELLED = 'cancelled', + AWAITING_EXECUTION = 'awaiting_execution', + PENDING = 'pending', +} +export type TransactionStatusValues = typeof TransactionStatus[keyof typeof TransactionStatus] + +export enum PendingActionType { + CONFIRM = 'confirm', + REJECT = 'reject', +} +export type PendingActionValues = PendingActionType[keyof PendingActionType] + +export type TransactionProps = { + baseGas: number + blockNumber?: number | null + cancelled?: boolean + confirmations: import('immutable').List + creationTx: boolean + customTx: boolean + data?: string | null + decimals?: (number | string) | null + decodedParams: import('src/logic/contracts/methodIds').DecodedMethods + executionDate?: string | null + executionTxHash?: string | null + executor: string + gasPrice: number + gasToken: string + isCancellationTx: boolean + isCollectibleTransfer: boolean + isExecuted: boolean + isPending?: boolean + isSuccessful: boolean + isTokenTransfer: boolean + modifySettingsTx: boolean + multiSendTx: boolean + nonce?: number | null + operation: number + origin: string | null + ownersWithPendingActions: import('immutable').Map> + recipient: string + refundParams: any + refundReceiver: string + safeTxGas: number + safeTxHash: string + status?: TransactionStatus + submissionDate?: string | null + symbol?: string | null + type: TransactionTypes + upgradeTx: boolean + value: string +} + +export type Transaction = import('immutable').RecordOf