Merge pull request #57 from gnosis/feature/integration-tx-history-service

Feature: Send TXs info to Safe Tx History service
This commit is contained in:
Adolfo Panizo 2018-08-10 10:22:29 +02:00 committed by GitHub
commit cce45b92bd
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 204 additions and 27 deletions

View File

@ -128,7 +128,7 @@
"<rootDir>/src/**/?(*.)(spec|test).js?(x)"
],
"testEnvironment": "node",
"testURL": "https://safe-react",
"testURL": "http://localhost:8000",
"transform": {
"^.+\\.(js|jsx)$": "<rootDir>/node_modules/babel-jest",
"^.+\\.(css|scss)$": "<rootDir>/config/jest/cssTransform.js",

View File

@ -0,0 +1,8 @@
// @flow
import { TX_SERVICE_HOST } from '~/config/names'
const devConfig = {
[TX_SERVICE_HOST]: 'https://safe-transaction-history.dev.gnosisdev.com/api/v1/',
}
export default devConfig

28
src/config/index.js Normal file
View File

@ -0,0 +1,28 @@
// @flow
import { ensureOnce } from '~/utils/singleton'
import { TX_SERVICE_HOST } from '~/config/names'
import devConfig from './development'
import testConfig from './testing'
import prodConfig from './production'
const configuration = () => {
if (process.env.NODE_ENV === 'test') {
return testConfig
}
if (process.env.NODE_ENV === 'production') {
return prodConfig
}
return devConfig
}
const getConfig = ensureOnce(configuration)
export const getTxServiceHost = () => {
const config = getConfig()
return config[TX_SERVICE_HOST]
}
export const getTxServiceUriFrom = (safeAddress: string) => `safes/${safeAddress}/transactions/`

3
src/config/names.js Normal file
View File

@ -0,0 +1,3 @@
// @flow
export const TX_SERVICE_HOST = 'tsh'

8
src/config/production.js Normal file
View File

@ -0,0 +1,8 @@
// @flow
import { TX_SERVICE_HOST } from '~/config/names'
const prodConfig = {
[TX_SERVICE_HOST]: 'https://safe-transaction-history.dev.gnosisdev.com/api/v1/',
}
export default prodConfig

8
src/config/testing.js Normal file
View File

@ -0,0 +1,8 @@
// @flow
import { TX_SERVICE_HOST } from '~/config/names'
const testConfig = {
[TX_SERVICE_HOST]: 'http://localhost:8000/api/v1/',
}
export default testConfig

View File

@ -8,8 +8,9 @@ import { getGnosisSafeContract } from '~/wallets/safeContracts'
import { getWeb3 } from '~/wallets/getWeb3'
import { type Safe } from '~/routes/safe/store/model/safe'
import { sameAddress } from '~/wallets/ethAddresses'
import { checkReceiptStatus, calculateGasOf, calculateGasPrice, EMPTY_DATA } from '~/wallets/ethTransactions'
import { EMPTY_DATA } from '~/wallets/ethTransactions'
import { storeSubject } from '~/utils/localStorage/transactions'
import { executeTransaction, approveTransaction } from '~/wallets/safeOperations'
export const TX_NAME_PARAM = 'txName'
export const TX_DESTINATION_PARAM = 'txDestination'
@ -93,39 +94,34 @@ export const getSafeEthereumInstance = async (safeAddress: string) => {
export const createTransaction = async (
safe: Safe,
txName: string,
txDest: string,
txValue: number,
name: string,
to: string,
value: number,
nonce: number,
user: string,
sender: string,
data: string = EMPTY_DATA,
) => {
const web3 = getWeb3()
const owners = safe.get('owners')
const safeAddress = safe.get('address')
const gnosisSafe = await getSafeEthereumInstance(safeAddress)
const valueInWei = web3.toWei(txValue, 'ether')
const threshold = safe.get('threshold')
const valueInWei = web3.toWei(value, 'ether')
const CALL = 0
const gasPrice = await calculateGasPrice()
const thresholdIsOne = safe.get('threshold') === 1
if (hasOneOwner(safe) || thresholdIsOne) {
const txConfirmationData =
gnosisSafe.contract.execTransactionIfApproved.getData(txDest, valueInWei, data, CALL, nonce)
const gas = await calculateGasOf(txConfirmationData, user, safeAddress)
const txHash =
await gnosisSafe.execTransactionIfApproved(txDest, valueInWei, data, CALL, nonce, { from: user, gas, gasPrice })
await checkReceiptStatus(txHash.tx)
const executedConfirmations: List<Confirmation> = buildExecutedConfirmationFrom(safe.get('owners'), user)
return storeTransaction(txName, nonce, txDest, txValue, user, executedConfirmations, txHash.tx, safeAddress, safe.get('threshold'), data)
const isExecution = hasOneOwner(safe) || threshold === 1
if (isExecution) {
const txHash = await executeTransaction(safeAddress, to, valueInWei, data, CALL, nonce, sender)
// TODO Remove when TX History service is fully integrated
const executedConfirmations: List<Confirmation> = buildExecutedConfirmationFrom(owners, sender)
// TODO Remove when TX History service is fully integrated
return storeTransaction(name, nonce, to, value, sender, executedConfirmations, txHash, safeAddress, threshold, data)
}
const txData = gnosisSafe.contract.approveTransactionWithParameters.getData(txDest, valueInWei, data, CALL, nonce)
const gas = await calculateGasOf(txData, user, safeAddress)
const txConfirmationHash = await gnosisSafe
.approveTransactionWithParameters(txDest, valueInWei, data, CALL, nonce, { from: user, gas, gasPrice })
await checkReceiptStatus(txConfirmationHash.tx)
const txHash = await approveTransaction(safeAddress, to, valueInWei, data, CALL, nonce, sender)
// TODO Remove when TX History service is fully integrated
const confirmations: List<Confirmation> = buildConfirmationsFrom(owners, sender, txHash)
const confirmations: List<Confirmation> = buildConfirmationsFrom(safe.get('owners'), user, txConfirmationHash.tx)
return storeTransaction(txName, nonce, txDest, txValue, user, confirmations, '', safeAddress, safe.get('threshold'), data)
// TODO Remove when TX History service is fully integrated
return storeTransaction(name, nonce, to, value, sender, confirmations, '', safeAddress, threshold, data)
}

View File

@ -0,0 +1,54 @@
// @flow
import { getSafeEthereumInstance } from '~/wallets/createTransactions'
import { calculateGasOf, checkReceiptStatus, calculateGasPrice } from '~/wallets/ethTransactions'
import { type Operation, submitOperation } from '~/wallets/safeTxHistory'
export const approveTransaction = async (
safeAddress: string,
to: string,
valueInWei: number,
data: string,
operation: Operation,
nonce: number,
sender: string,
) => {
const gasPrice = await calculateGasPrice()
const gnosisSafe = await getSafeEthereumInstance(safeAddress)
const txData = gnosisSafe.contract.approveTransactionWithParameters.getData(to, valueInWei, data, operation, nonce)
const gas = await calculateGasOf(txData, sender, safeAddress)
const txReceipt = await gnosisSafe
.approveTransactionWithParameters(to, valueInWei, data, operation, nonce, { from: sender, gas, gasPrice })
const txHash = txReceipt.tx
await checkReceiptStatus(txHash)
await submitOperation(safeAddress, to, valueInWei, data, operation, nonce, txHash, sender, 'confirmation')
return txHash
}
export const executeTransaction = async (
safeAddress: string,
to: string,
valueInWei: number,
data: string,
operation: Operation,
nonce: number,
sender: string,
) => {
const gasPrice = await calculateGasPrice()
const gnosisSafe = await getSafeEthereumInstance(safeAddress)
const txConfirmationData =
gnosisSafe.contract.execTransactionIfApproved.getData(to, valueInWei, data, operation, nonce)
const gas = await calculateGasOf(txConfirmationData, sender, safeAddress)
const txReceipt =
await gnosisSafe.execTransactionIfApproved(to, valueInWei, data, operation, nonce, { from: sender, gas, gasPrice })
const txHash = txReceipt.tx
await checkReceiptStatus(txHash)
await submitOperation(safeAddress, to, valueInWei, data, operation, nonce, txHash, sender, 'execution')
return txHash
}

View File

@ -0,0 +1,72 @@
// @flow
import { getSafeEthereumInstance } from '~/wallets/createTransactions'
import { getWeb3 } from '~/wallets/getWeb3'
import { getTxServiceUriFrom, getTxServiceHost } from '~/config'
type Type = 'confirmation' | 'execution'
export type Operation = 0 | 1 | 2
const calculateBodyFrom = async (
safeAddress: string,
to: string,
valueInWei: number,
data: string,
operation: Operation,
nonce: number,
transactionHash: string,
sender: string,
type: Type,
) => {
const gnosisSafe = await getSafeEthereumInstance(safeAddress)
const contractTransactionHash = await gnosisSafe.getTransactionHash(to, valueInWei, data, operation, nonce)
return JSON.stringify({
to: getWeb3().toChecksumAddress(to),
value: valueInWei,
data,
operation,
nonce,
contractTransactionHash,
transactionHash,
sender: getWeb3().toChecksumAddress(sender),
type,
})
}
const buildTxServiceUrlFrom = (safeAddress: string) => {
const host = getTxServiceHost()
const address = getWeb3().toChecksumAddress(safeAddress)
const base = getTxServiceUriFrom(address)
return `${host}${base}`
}
export const submitOperation = async (
safeAddress: string,
to: string,
valueInWei: number,
data: string,
operation: Operation,
nonce: number,
txHash: string,
sender: string,
type: Type,
) => {
const url = buildTxServiceUrlFrom(safeAddress)
const headers = {
Accept: 'application/json',
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
}
const body = await calculateBodyFrom(safeAddress, to, valueInWei, data, operation, nonce, txHash, sender, type)
const response = await fetch(url, {
method: 'POST',
headers,
body,
})
if (response.status !== 202) {
return Promise.reject(new Error('Error submitting the transaction'))
}
return Promise.resolve()
}