Skip to content
This repository was archived by the owner on Nov 10, 2023. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
7ca838d
WIP: first attempt to send funds using spending limit module
fernandomg Aug 25, 2020
bbacc8b
fix: avoid signature request
fernandomg Aug 26, 2020
d228c03
refactor: remove duplicated type file
fernandomg Aug 26, 2020
3380816
add missing type to parameters
fernandomg Aug 26, 2020
96d0776
load module transactions in the store
fernandomg Aug 26, 2020
6411d5d
refactor: move logic and types from `routes` to `logic`
fernandomg Aug 28, 2020
e100876
update module's reducer to only add new txs
fernandomg Aug 28, 2020
7d5cd59
define type for SafeModuleTransaction
fernandomg Aug 28, 2020
f7f3dc2
add moduleTxs to the list of txs
fernandomg Aug 28, 2020
0aa49d9
add ExpandedModuleTx component
fernandomg Aug 28, 2020
a7b5953
identify spendingLimitTxs
fernandomg Aug 28, 2020
0ea23b3
Merge branch 'feature/#693-sendFunds-SpendingLimit' into feature/#691…
fernandomg Aug 28, 2020
91167fc
Merge branch 'feature/#693-sendFunds-SpendingLimit' into feature/#691…
fernandomg Sep 1, 2020
08faadb
Merge branch 'feature/#693-sendFunds-SpendingLimit' into feature/#691…
fernandomg Sep 3, 2020
80d0948
fix: properly verify existence of moduleTransactions
fernandomg Sep 3, 2020
c141d52
fix: remove unnecessary state access from within `loadModuleTransacti…
fernandomg Sep 3, 2020
a549155
fix: rewrite explanation why there's a filter in the module's tx reducer
fernandomg Sep 3, 2020
f2668fa
fix: remove `as any` and unnecessary params in createTransaction acti…
fernandomg Sep 3, 2020
b138c4d
fix: remove index from key
fernandomg Sep 3, 2020
ef439dd
refactor: rename constants
fernandomg Sep 3, 2020
5840ae9
Merge branch 'feature/#413-SpendingLimit-in-app' into feature/#691-tx…
fernandomg Sep 3, 2020
ce681b8
fix: support ETH for spending limit transactions
fernandomg Sep 8, 2020
6c56e9b
Merge branch 'feature/#413-SpendingLimit-in-app' into feature/#691-tx…
fernandomg Sep 8, 2020
b58b648
fix types after merge
fernandomg Sep 8, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/config/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,9 @@ export const getTxServiceHost = () => {
export const getTxServiceUriFrom = (safeAddress) =>
`safes/${safeAddress}/transactions/`

export const getModuleTxServiceUriFrom = (safeAddress) =>
`safes/${safeAddress}/module-transactions/`

export const getIncomingTxServiceUriTo = (safeAddress) =>
`safes/${safeAddress}/incoming-transfers/`

Expand Down
49 changes: 21 additions & 28 deletions src/logic/contracts/methodIds.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,20 @@
import { web3ReadOnly as web3 } from 'src/logic/wallets/getWeb3'
import { DataDecoded, METHOD_TO_ID } from 'src/routes/safe/store/models/types/transactions.d'
import {
DataDecoded,
SAFE_METHOD_ID_TO_NAME,
SPENDING_LIMIT_METHOD_ID_TO_NAME,
SPENDING_LIMIT_METHODS_NAMES,
} from 'src/logic/safe/store/models/types/transactions.d'

export const decodeParamsFromSafeMethod = (data: string): DataDecoded | null => {
const [methodId, params] = [data.slice(0, 10) as keyof typeof METHOD_TO_ID | string, data.slice(10)]
const [methodId, params] = [data.slice(0, 10) as keyof typeof SAFE_METHOD_ID_TO_NAME | string, data.slice(10)]

switch (methodId) {
// swapOwner
case '0xe318b52b': {
const decodedParameters = web3.eth.abi.decodeParameters(['uint', 'address', 'address'], params) as string[]
return {
method: METHOD_TO_ID[methodId],
method: SAFE_METHOD_ID_TO_NAME[methodId],
parameters: [
{ name: 'oldOwner', type: 'address', value: decodedParameters[1] },
{ name: 'newOwner', type: 'address', value: decodedParameters[2] },
Expand All @@ -21,7 +26,7 @@ export const decodeParamsFromSafeMethod = (data: string): DataDecoded | null =>
case '0x0d582f13': {
const decodedParameters = web3.eth.abi.decodeParameters(['address', 'uint'], params)
return {
method: METHOD_TO_ID[methodId],
method: SAFE_METHOD_ID_TO_NAME[methodId],
parameters: [
{ name: 'owner', type: 'address', value: decodedParameters[0] },
{ name: '_threshold', type: 'uint', value: decodedParameters[1] },
Expand All @@ -33,7 +38,7 @@ export const decodeParamsFromSafeMethod = (data: string): DataDecoded | null =>
case '0xf8dc5dd9': {
const decodedParameters = web3.eth.abi.decodeParameters(['address', 'address', 'uint'], params)
return {
method: METHOD_TO_ID[methodId],
method: SAFE_METHOD_ID_TO_NAME[methodId],
parameters: [
{ name: 'oldOwner', type: 'address', value: decodedParameters[1] },
{ name: '_threshold', type: 'uint', value: decodedParameters[2] },
Expand All @@ -45,7 +50,7 @@ export const decodeParamsFromSafeMethod = (data: string): DataDecoded | null =>
case '0x694e80c3': {
const decodedParameters = web3.eth.abi.decodeParameters(['uint'], params)
return {
method: METHOD_TO_ID[methodId],
method: SAFE_METHOD_ID_TO_NAME[methodId],
parameters: [
{ name: '_threshold', type: 'uint', value: decodedParameters[0] },
],
Expand All @@ -56,7 +61,7 @@ export const decodeParamsFromSafeMethod = (data: string): DataDecoded | null =>
case '0x610b5925': {
const decodedParameters = web3.eth.abi.decodeParameters(['address'], params)
return {
method: METHOD_TO_ID[methodId],
method: SAFE_METHOD_ID_TO_NAME[methodId],
parameters: [
{ name: 'module', type: 'address', value: decodedParameters[0] },
],
Expand All @@ -67,7 +72,7 @@ export const decodeParamsFromSafeMethod = (data: string): DataDecoded | null =>
case '0xe009cfde': {
const decodedParameters = web3.eth.abi.decodeParameters(['address', 'address'], params)
return {
method: METHOD_TO_ID[methodId],
method: SAFE_METHOD_ID_TO_NAME[methodId],
parameters: [
{ name: 'prevModule', type: 'address', value: decodedParameters[0] },
{ name: 'module', type: 'address', value: decodedParameters[1] },
Expand All @@ -80,32 +85,20 @@ export const decodeParamsFromSafeMethod = (data: string): DataDecoded | null =>
}
}

export const SPENDING_LIMIT_METHODS_NAMES = {
ADD_DELEGATE: 'addDelegate',
SET_ALLOWANCE: 'setAllowance',
EXECUTE_ALLOWANCE_TRANSFER: 'executeAllowanceTransfer',
}

export const SPENDING_LIMIT_METHOD_TO_ID = {
'0xe71bdf41': SPENDING_LIMIT_METHODS_NAMES.ADD_DELEGATE,
'0xbeaeb388': SPENDING_LIMIT_METHODS_NAMES.SET_ALLOWANCE,
'0x4515641a': SPENDING_LIMIT_METHODS_NAMES.EXECUTE_ALLOWANCE_TRANSFER,
}

export const isSetAllowanceMethod = (data: string): boolean => {
const methodId = data.slice(0, 10) as keyof typeof SPENDING_LIMIT_METHOD_TO_ID
return SPENDING_LIMIT_METHOD_TO_ID[methodId] === SPENDING_LIMIT_METHODS_NAMES.SET_ALLOWANCE
const methodId = data.slice(0, 10) as keyof typeof SPENDING_LIMIT_METHOD_ID_TO_NAME
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Would be good to export this keyof typeof thing to a separate type

return SPENDING_LIMIT_METHOD_ID_TO_NAME[methodId] === SPENDING_LIMIT_METHODS_NAMES.SET_ALLOWANCE
}

export const decodeParamsFromSpendingLimit = (data: string): DataDecoded | null => {
const [methodId, params] = [data.slice(0, 10) as keyof typeof SPENDING_LIMIT_METHOD_TO_ID | string, data.slice(10)]
const [methodId, params] = [data.slice(0, 10) as keyof typeof SPENDING_LIMIT_METHOD_ID_TO_NAME | string, data.slice(10)]

switch (methodId) {
// addDelegate
case '0xe71bdf41': {
const decodedParameter = (web3.eth.abi.decodeParameter('address', params) as unknown) as string
return {
method: SPENDING_LIMIT_METHOD_TO_ID[methodId],
method: SPENDING_LIMIT_METHOD_ID_TO_NAME[methodId],
parameters: [
{ name: 'delegate', type: 'address', value: decodedParameter },
],
Expand All @@ -116,7 +109,7 @@ export const decodeParamsFromSpendingLimit = (data: string): DataDecoded | null
case '0xbeaeb388': {
const decodedParameters = web3.eth.abi.decodeParameters(['address', 'address', 'uint96', 'uint16', 'uint32'], params)
return {
method: SPENDING_LIMIT_METHOD_TO_ID[methodId],
method: SPENDING_LIMIT_METHOD_ID_TO_NAME[methodId],
parameters: [
{ name: 'delegate', type: 'address', value: decodedParameters[0] },
{ name: 'token', type: 'address', value: decodedParameters[1] },
Expand All @@ -131,7 +124,7 @@ export const decodeParamsFromSpendingLimit = (data: string): DataDecoded | null
case '0x4515641a': {
const decodedParameters = web3.eth.abi.decodeParameters(['address', 'address', 'address', 'uint96', 'address', 'uint96', 'address', 'bytes'], params)
return {
method: SPENDING_LIMIT_METHOD_TO_ID[methodId],
method: SPENDING_LIMIT_METHOD_ID_TO_NAME[methodId],
parameters: [
{ name: "safe", type: "address", value: decodedParameters[0] },
{ name: "token", type: "address", value: decodedParameters[1] },
Expand All @@ -151,11 +144,11 @@ export const decodeParamsFromSpendingLimit = (data: string): DataDecoded | null
}

const isSafeMethod = (methodId: string): boolean => {
return !!METHOD_TO_ID[methodId]
return !!SAFE_METHOD_ID_TO_NAME[methodId]
}

const isSpendingLimitMethod = (methodId: string): boolean => {
return !!SPENDING_LIMIT_METHOD_TO_ID[methodId]
return !!SPENDING_LIMIT_METHOD_ID_TO_NAME[methodId]
}

export const decodeMethods = (data: string | null): DataDecoded | null => {
Expand Down
13 changes: 13 additions & 0 deletions src/logic/safe/store/actions/addModuleTransactions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { createAction } from 'redux-actions'
import { ModuleTxServiceModel } from 'src/logic/safe/store/actions/transactions/fetchTransactions/loadModuleTransactions'

export const ADD_MODULE_TRANSACTIONS = 'ADD_MODULE_TRANSACTIONS'

export type AddModuleTransactionsAction = {
payload: {
safeAddress: string
modules: ModuleTxServiceModel[]
}
}

export const addModuleTransactions = createAction(ADD_MODULE_TRANSACTIONS)
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,25 @@ import axios from 'axios'

import { buildTxServiceUrl } from 'src/logic/safe/transactions'
import { buildIncomingTxServiceUrl } from 'src/logic/safe/transactions/incomingTxHistory'
import { buildModuleTxServiceUrl } from 'src/logic/safe/transactions/moduleTxHistory'
import { TxServiceModel } from 'src/logic/safe/store/actions/transactions/fetchTransactions/loadOutgoingTransactions'
import { IncomingTxServiceModel } from 'src/logic/safe/store/actions/transactions/fetchTransactions/loadIncomingTransactions'
import { TransactionTypes } from 'src/logic/safe/store/models/types/transaction'
import { ModuleTxServiceModel } from './loadModuleTransactions'

const getServiceUrl = (txType: string, safeAddress: string): string => {
return {
[TransactionTypes.INCOMING]: buildIncomingTxServiceUrl,
[TransactionTypes.OUTGOING]: buildTxServiceUrl,
[TransactionTypes.MODULE]: buildModuleTxServiceUrl,
}[txType](safeAddress)
}

async function fetchTransactions(
txType: TransactionTypes.MODULE,
safeAddress: string,
eTag: string | null,
): Promise<{ eTag: string | null; results: ModuleTxServiceModel[] }>
async function fetchTransactions(
txType: TransactionTypes.INCOMING,
safeAddress: string,
Expand All @@ -24,10 +32,10 @@ async function fetchTransactions(
eTag: string | null,
): Promise<{ eTag: string | null; results: TxServiceModel[] }>
async function fetchTransactions(
txType: TransactionTypes.INCOMING | TransactionTypes.OUTGOING,
txType: TransactionTypes.MODULE | TransactionTypes.INCOMING | TransactionTypes.OUTGOING,
safeAddress: string,
eTag: string | null,
): Promise<{ eTag: string | null; results: TxServiceModel[] | IncomingTxServiceModel[] }> {
): Promise<{ eTag: string | null; results: ModuleTxServiceModel[] | TxServiceModel[] | IncomingTxServiceModel[] }> {
try {
const url = getServiceUrl(txType, safeAddress)
const response = await axios.get(url, eTag ? { headers: { 'If-None-Match': eTag } } : undefined)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
import { backOff } from 'exponential-backoff'
import { batch } from 'react-redux'
import { ThunkAction, ThunkDispatch } from 'redux-thunk'
import { AnyAction } from 'redux'
import { backOff } from 'exponential-backoff'

import { addIncomingTransactions } from '../../addIncomingTransactions'

import { loadIncomingTransactions } from './loadIncomingTransactions'
import { loadOutgoingTransactions } from './loadOutgoingTransactions'
import { ThunkAction, ThunkDispatch } from 'redux-thunk'

import { addIncomingTransactions } from 'src/logic/safe/store/actions/addIncomingTransactions'
import { addModuleTransactions } from 'src/logic/safe/store/actions/addModuleTransactions'
import { addOrUpdateCancellationTransactions } from 'src/logic/safe/store/actions/transactions/addOrUpdateCancellationTransactions'
import { addOrUpdateTransactions } from 'src/logic/safe/store/actions/transactions/addOrUpdateTransactions'
import { AppReduxState } from 'src/store'

import { loadIncomingTransactions } from './loadIncomingTransactions'
import { loadModuleTransactions } from './loadModuleTransactions'
import { loadOutgoingTransactions } from './loadOutgoingTransactions'

const noFunc = () => {}

export default (safeAddress: string): ThunkAction<Promise<void>, AppReduxState, undefined, AnyAction> => async (
Expand Down Expand Up @@ -44,6 +45,12 @@ export default (safeAddress: string): ThunkAction<Promise<void>, AppReduxState,
if (safeIncomingTxs?.size) {
dispatch(addIncomingTransactions(incomingTransactions))
}

const moduleTransactions = await loadModuleTransactions(safeAddress)

if (moduleTransactions.length) {
dispatch(addModuleTransactions({ modules: moduleTransactions, safeAddress }))
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Little inconsistency in naming, transactions get passed as modules

}
} catch (error) {
console.log('Error fetching transactions:', error)
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import fetchTransactions from 'src/logic/safe/store/actions/transactions/fetchTransactions/fetchTransactions'
import { TransactionTypes } from 'src/logic/safe/store/models/types/transaction'
import { DataDecoded, Operation } from 'src/logic/safe/store/models/types/transactions.d'

export type ModuleTxServiceModel = {
created: string
executionDate: string
blockNumber: number
transactionHash: string
safe: string
module: string
to: string
value: string
data: string
operation: Operation
dataDecoded: DataDecoded
}

type ETag = string | null

let previousETag: ETag = null
export const loadModuleTransactions = async (safeAddress: string): Promise<ModuleTxServiceModel[]> => {
if (!safeAddress) {
return []
}

const { eTag, results }: { eTag: ETag; results: ModuleTxServiceModel[] } = await fetchTransactions(
TransactionTypes.MODULE,
safeAddress,
previousETag,
)
previousETag = eTag

return results
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,8 @@ import {
extractERC721TransferDetails,
extractETHTransferDetails,
extractUnknownTransferDetails,
} from './transferDetails'
import { isMultiSendParameter } from './newTransactionHelpers'
} from 'src/logic/safe/store/actions/transactions/utils/transferDetails'
import { isMultiSendParameter } from 'src/logic/safe/store/actions/transactions/utils/newTransactionHelpers'
import { Transaction } from 'src/logic/safe/store/models/types/transaction'

export type MultiSendDetails = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import {
Parameter,
Transaction,
TxType,
} from 'src/routes/safe/store/models/types/transactions.d'
} from 'src/logic/safe/store/models/types/transactions.d'

export const isMultiSigTx = (tx: Transaction): tx is MultiSigTransaction => {
return TxType[tx.txType] === TxType.MULTISIG_TRANSACTION
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ import { TypedDataUtils } from 'eth-sig-util'
import { Token } from 'src/logic/tokens/store/model/token'
import { ProviderRecord } from 'src/logic/wallets/store/model/provider'
import { SafeRecord } from 'src/logic/safe/store/models/safe'
import { DecodedParams } from 'src/routes/safe/store/models/types/transactions.d'
import { DecodedParams } from 'src/logic/safe/store/models/types/transactions.d'

export const isEmptyData = (data?: string | null): boolean => {
return !data || data === EMPTY_DATA
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Transfer, TxConstants } from 'src/routes/safe/store/models/types/transactions.d'
import { Transfer, TxConstants } from 'src/logic/safe/store/models/types/transactions.d'
import { sameAddress } from 'src/logic/wallets/ethAddresses'
import { store } from 'src/store'
import { safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors'
Expand All @@ -7,7 +7,7 @@ import {
ERC721TransferDetails,
ETHTransferDetails,
UnknownTransferDetails,
} from './transferDetails.d'
} from 'src/logic/safe/store/actions/transactions/utils/transferDetails.d'
import { humanReadableValue } from 'src/logic/tokens/utils/humanReadableValue'

const isIncomingTransfer = (transfer: Transfer): boolean => {
Expand Down
16 changes: 15 additions & 1 deletion src/logic/safe/store/models/types/transaction.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { List, Map, RecordOf } from 'immutable'
import { ModuleTxServiceModel } from 'src/logic/safe/store/actions/transactions/fetchTransactions/loadModuleTransactions'
import { Token } from 'src/logic/tokens/store/model/token'
import { Confirmation } from './confirmation'
import { GnosisSafe } from 'src/types/contracts/GnosisSafe.d'
import { DataDecoded, Transfer } from './transactions'
import { DecodedParams } from 'src/routes/safe/store/models/types/transactions'
import { DecodedParams } from 'src/logic/safe/store/models/types/transactions'

export enum TransactionTypes {
INCOMING = 'incoming',
Expand All @@ -14,6 +16,8 @@ export enum TransactionTypes {
UPGRADE = 'upgrade',
TOKEN = 'token',
COLLECTIBLE = 'collectible',
MODULE = 'module',
SPENDING_LIMIT = 'spendingLimit',
}
export type TransactionTypeValues = typeof TransactionTypes[keyof typeof TransactionTypes]

Expand Down Expand Up @@ -101,3 +105,13 @@ export type TxArgs = {
to: string
valueInWei: string
}

export type SafeModuleTransaction = ModuleTxServiceModel & {
nonce?: string // not required for this tx: added for compatibility
fee?: number // not required for this tx: added for compatibility
executionTxHash?: string // not required for this tx: added for compatibility
safeTxHash: string // table uses this key as a unique row identifier, added for compatibility
status: TransactionStatus
type: TransactionTypes
tokenInfo?: Token
}
14 changes: 13 additions & 1 deletion src/logic/safe/store/models/types/transactions.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ export const SAFE_METHODS_NAMES = {
DISABLE_MODULE: 'disableModule',
}

export const METHOD_TO_ID = {
export const SAFE_METHOD_ID_TO_NAME = {
'0xe318b52b': SAFE_METHODS_NAMES.SWAP_OWNER,
'0x0d582f13': SAFE_METHODS_NAMES.ADD_OWNER_WITH_THRESHOLD,
'0xf8dc5dd9': SAFE_METHODS_NAMES.REMOVE_OWNER,
Expand All @@ -239,6 +239,18 @@ export const METHOD_TO_ID = {
'0xe009cfde': SAFE_METHODS_NAMES.DISABLE_MODULE,
}

export const SPENDING_LIMIT_METHODS_NAMES = {
ADD_DELEGATE: 'addDelegate',
SET_ALLOWANCE: 'setAllowance',
EXECUTE_ALLOWANCE_TRANSFER: 'executeAllowanceTransfer',
}

export const SPENDING_LIMIT_METHOD_ID_TO_NAME = {
'0xe71bdf41': SPENDING_LIMIT_METHODS_NAMES.ADD_DELEGATE,
'0xbeaeb388': SPENDING_LIMIT_METHODS_NAMES.SET_ALLOWANCE,
'0x4515641a': SPENDING_LIMIT_METHODS_NAMES.EXECUTE_ALLOWANCE_TRANSFER,
}

export type SafeMethods = typeof SAFE_METHODS_NAMES[keyof typeof SAFE_METHODS_NAMES]

type TokenMethods = 'transfer' | 'transferFrom' | 'safeTransferFrom'
Expand Down
Loading