mirror of
https://github.com/status-im/safe-react.git
synced 2025-02-17 12:07:09 +00:00
Merge branch 'development' of github.com:gnosis/safe-react into development
This commit is contained in:
commit
567de80afb
25
.github/workflows/deploy-ewc.yml
vendored
25
.github/workflows/deploy-ewc.yml
vendored
@ -11,7 +11,7 @@ on:
|
||||
|
||||
env:
|
||||
REPO_NAME_ALPHANUMERIC: safereact
|
||||
REACT_APP_NETWORK: 'ewc'
|
||||
REACT_APP_NETWORK: 'energy_web_chain'
|
||||
STAGING_BUCKET_NAME: ${{ secrets.STAGING_EWC_BUCKET_NAME }}
|
||||
REACT_APP_SENTRY_DSN: ${{ secrets.SENTRY_DSN_EWC }}
|
||||
REACT_APP_GOOGLE_ANALYTICS: ${{ secrets.REACT_APP_GOOGLE_ANALYTICS_ID_EWC }}
|
||||
@ -63,7 +63,7 @@ jobs:
|
||||
|
||||
# Set production flag
|
||||
- name: Set production flag for tag build
|
||||
run: echo "REACT_APP_ENV='production'" >> $GITHUB_ENV
|
||||
run: echo "REACT_APP_ENV=production" >> $GITHUB_ENV
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
|
||||
- name: Build ${{ env.REACT_APP_NETWORK }} app
|
||||
@ -111,18 +111,21 @@ jobs:
|
||||
# Script to deploy to staging environment
|
||||
- name: 'Deploy to S3: Staging'
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: aws s3 sync build s3://${{ secrets.STAGING_BUCKET_NAME }}/current/app --delete
|
||||
run: aws s3 sync build s3://${{ env.STAGING_BUCKET_NAME }}/current/app --delete
|
||||
|
||||
# Script to upload release files
|
||||
- run: bash ./scripts/github/deploy_release.sh
|
||||
- name: 'Upload release build files for production'
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
env:
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
PR_NUMBER: ${{ github.event.number }}
|
||||
REVIEW_BUCKET_NAME: ${{ secrets.AWS_REVIEW_BUCKET_NAME }}
|
||||
REACT_APP_NETWORK: ${{ env.REACT_APP_NETWORK }}
|
||||
VERSION_TAG: ${{ github.event.release.tag_name }}
|
||||
run: aws s3 sync build s3://${{ env.STAGING_BUCKET_NAME }}/releases/${{ github.event.release.tag_name }} --delete
|
||||
# - run: bash ./scripts/github/deploy_release.sh
|
||||
# if: startsWith(github.ref, 'refs/tags/v')
|
||||
# env:
|
||||
# AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
# AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
# PR_NUMBER: ${{ github.event.number }}
|
||||
# REVIEW_BUCKET_NAME: ${{ secrets.AWS_REVIEW_BUCKET_NAME }}
|
||||
# REACT_APP_NETWORK: ${{ env.REACT_APP_NETWORK }}
|
||||
# VERSION_TAG: ${{ github.event.release.tag_name }}
|
||||
|
||||
# Script to prepare production deployments
|
||||
- run: bash ./scripts/github/prepare_production_deployment.sh
|
||||
|
23
.github/workflows/deploy-mainnet.yml
vendored
23
.github/workflows/deploy-mainnet.yml
vendored
@ -63,7 +63,7 @@ jobs:
|
||||
|
||||
# Set production flag
|
||||
- name: Set production flag for tag build
|
||||
run: echo "REACT_APP_ENV='production'" >> $GITHUB_ENV
|
||||
run: echo "REACT_APP_ENV=production" >> $GITHUB_ENV
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
|
||||
- name: Build ${{ env.REACT_APP_NETWORK }} app
|
||||
@ -92,18 +92,21 @@ jobs:
|
||||
# Script to deploy to staging environment
|
||||
- name: 'Deploy to S3: Staging'
|
||||
if: github.ref == 'refs/heads/master' # Or refs/heads/main
|
||||
run: aws s3 sync build s3://${{ secrets.STAGING_BUCKET_NAME }}/current/app --delete
|
||||
run: aws s3 sync build s3://${{ env.STAGING_BUCKET_NAME }}/current/app --delete
|
||||
|
||||
# Script to upload release files
|
||||
- run: bash ./scripts/github/deploy_release.sh
|
||||
- name: 'Upload release build files for production'
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
env:
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
PR_NUMBER: ${{ github.event.number }}
|
||||
REVIEW_BUCKET_NAME: ${{ secrets.AWS_REVIEW_BUCKET_NAME }}
|
||||
REACT_APP_NETWORK: ${{ env.REACT_APP_NETWORK }}
|
||||
VERSION_TAG: ${{ github.event.release.tag_name }}
|
||||
run: aws s3 sync build s3://${{ env.STAGING_BUCKET_NAME }}/releases/${{ github.event.release.tag_name }} --delete
|
||||
# - run: bash ./scripts/github/deploy_release.sh
|
||||
# if: startsWith(github.ref, 'refs/tags/v')
|
||||
# env:
|
||||
# AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
# AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
# PR_NUMBER: ${{ github.event.number }}
|
||||
# REVIEW_BUCKET_NAME: ${{ secrets.AWS_REVIEW_BUCKET_NAME }}
|
||||
# REACT_APP_NETWORK: ${{ env.REACT_APP_NETWORK }}
|
||||
# VERSION_TAG: ${{ github.event.release.tag_name }}
|
||||
|
||||
# Script to prepare production deployments
|
||||
- run: bash ./scripts/github/prepare_production_deployment.sh
|
||||
|
25
.github/workflows/deploy-rinkeby.yml
vendored
25
.github/workflows/deploy-rinkeby.yml
vendored
@ -67,7 +67,7 @@ jobs:
|
||||
|
||||
# Set production flag
|
||||
- name: Set production flag for tag build
|
||||
run: echo "REACT_APP_ENV='production'" >> $GITHUB_ENV
|
||||
run: echo "REACT_APP_ENV=production" >> $GITHUB_ENV
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
|
||||
- name: Build ${{ env.REACT_APP_NETWORK }} app ${{ env.REACT_APP_ENV }}
|
||||
@ -118,20 +118,23 @@ jobs:
|
||||
# Script to deploy to staging environment
|
||||
- name: 'Deploy to S3: Staging'
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: aws s3 sync build s3://${{ secrets.STAGING_BUCKET_NAME }}/current/app --delete
|
||||
run: aws s3 sync build s3://${{ env.STAGING_BUCKET_NAME }}/current/app --delete
|
||||
|
||||
# Script to upload release files
|
||||
- run: bash ./scripts/github/deploy_release.sh
|
||||
- name: 'Upload release build files for production'
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
env:
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
PR_NUMBER: ${{ github.event.number }}
|
||||
REVIEW_BUCKET_NAME: ${{ secrets.AWS_REVIEW_BUCKET_NAME }}
|
||||
REACT_APP_NETWORK: ${{ env.REACT_APP_NETWORK }}
|
||||
VERSION_TAG: ${{ github.event.release.tag_name }}
|
||||
run: aws s3 sync build s3://${{ env.STAGING_BUCKET_NAME }}/releases/${{ github.event.release.tag_name }} --delete
|
||||
# - run: bash ./scripts/github/deploy_release.sh
|
||||
# if: startsWith(github.ref, 'refs/tags/v')
|
||||
# env:
|
||||
# AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
# AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
# PR_NUMBER: ${{ github.event.number }}
|
||||
# REVIEW_BUCKET_NAME: ${{ secrets.AWS_REVIEW_BUCKET_NAME }}
|
||||
# REACT_APP_NETWORK: ${{ env.REACT_APP_NETWORK }}
|
||||
# VERSION_TAG: ${{ github.event.release.tag_name }}
|
||||
|
||||
# Script to prepare production deployments
|
||||
# Script to notify production deployments
|
||||
- run: bash ./scripts/github/prepare_production_deployment.sh
|
||||
if: success() && startsWith(github.ref, 'refs/tags/v')
|
||||
env:
|
||||
|
24
.github/workflows/deploy-volta.yml
vendored
24
.github/workflows/deploy-volta.yml
vendored
@ -66,7 +66,7 @@ jobs:
|
||||
|
||||
# Set production flag
|
||||
- name: Set production flag for tag build
|
||||
run: echo "REACT_APP_ENV='production'" >> $GITHUB_ENV
|
||||
run: echo "REACT_APP_ENV=production" >> $GITHUB_ENV
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
|
||||
- name: Build ${{ env.REACT_APP_NETWORK }} app
|
||||
@ -115,19 +115,21 @@ jobs:
|
||||
# Script to deploy to staging environment
|
||||
- name: 'Deploy to S3: Staging'
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: aws s3 sync build s3://${{ secrets.STAGING_BUCKET_NAME }}/current/app --delete
|
||||
run: aws s3 sync build s3://${{ env.STAGING_BUCKET_NAME }}/current/app --delete
|
||||
|
||||
# Script to upload release files
|
||||
- run: bash ./scripts/github/deploy_release.sh
|
||||
- name: 'Upload release build files for production'
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
env:
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
PR_NUMBER: ${{ github.event.number }}
|
||||
REVIEW_BUCKET_NAME: ${{ secrets.AWS_REVIEW_BUCKET_NAME }}
|
||||
REACT_APP_NETWORK: ${{ env.REACT_APP_NETWORK }}
|
||||
VERSION_TAG: ${{ github.event.release.tag_name }}
|
||||
|
||||
run: aws s3 sync build s3://${{ env.STAGING_BUCKET_NAME }}/releases/${{ github.event.release.tag_name }} --delete
|
||||
# - run: bash ./scripts/github/deploy_release.sh
|
||||
# if: startsWith(github.ref, 'refs/tags/v')
|
||||
# env:
|
||||
# AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
# AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
# PR_NUMBER: ${{ github.event.number }}
|
||||
# REVIEW_BUCKET_NAME: ${{ secrets.AWS_REVIEW_BUCKET_NAME }}
|
||||
# REACT_APP_NETWORK: ${{ env.REACT_APP_NETWORK }}
|
||||
# VERSION_TAG: ${{ github.event.release.tag_name }}
|
||||
# Script to prepare production deployments
|
||||
- run: bash ./scripts/github/prepare_production_deployment.sh
|
||||
if: success() && startsWith(github.ref, 'refs/tags/v')
|
||||
|
23
.github/workflows/deploy-xdai.yml
vendored
23
.github/workflows/deploy-xdai.yml
vendored
@ -66,7 +66,7 @@ jobs:
|
||||
|
||||
# Set production flag
|
||||
- name: Set production flag for tag build
|
||||
run: echo "REACT_APP_ENV='production'" >> $GITHUB_ENV
|
||||
run: echo "REACT_APP_ENV=production" >> $GITHUB_ENV
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
|
||||
- name: Build ${{ env.REACT_APP_NETWORK }} app
|
||||
@ -114,18 +114,21 @@ jobs:
|
||||
# Script to deploy to staging environment
|
||||
- name: 'Deploy to S3: Staging'
|
||||
if: github.ref == 'refs/heads/master'
|
||||
run: aws s3 sync build s3://${{ secrets.STAGING_BUCKET_NAME }}/current/app --delete
|
||||
run: aws s3 sync build s3://${{ env.STAGING_BUCKET_NAME }}/current/app --delete
|
||||
|
||||
# Script to upload release files
|
||||
- run: bash ./scripts/github/deploy_release.sh
|
||||
- name: 'Upload release build files for production'
|
||||
if: startsWith(github.ref, 'refs/tags/v')
|
||||
env:
|
||||
AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
PR_NUMBER: ${{ github.event.number }}
|
||||
REVIEW_BUCKET_NAME: ${{ secrets.AWS_REVIEW_BUCKET_NAME }}
|
||||
REACT_APP_NETWORK: ${{ env.REACT_APP_NETWORK }}
|
||||
VERSION_TAG: ${{ github.event.release.tag_name }}
|
||||
run: aws s3 sync build s3://${{ env.STAGING_BUCKET_NAME }}/releases/${{ github.event.release.tag_name }} --delete
|
||||
# - run: bash ./scripts/github/deploy_release.sh
|
||||
# if: startsWith(github.ref, 'refs/tags/v')
|
||||
# env:
|
||||
# AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
|
||||
# AWS_ACCESS_KEY_ID: ${{ secrets.AWS_ACCESS_KEY_ID }}
|
||||
# PR_NUMBER: ${{ github.event.number }}
|
||||
# REVIEW_BUCKET_NAME: ${{ secrets.AWS_REVIEW_BUCKET_NAME }}
|
||||
# REACT_APP_NETWORK: ${{ env.REACT_APP_NETWORK }}
|
||||
# VERSION_TAG: ${{ github.event.release.tag_name }}
|
||||
|
||||
# Script to prepare production deployments
|
||||
- run: bash ./scripts/github/prepare_production_deployment.sh
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "safe-react",
|
||||
"version": "3.0.1",
|
||||
"version": "3.1.2",
|
||||
"description": "Allowing crypto users manage funds in a safer way",
|
||||
"website": "https://github.com/gnosis/safe-react#readme",
|
||||
"bugs": {
|
||||
|
@ -11,6 +11,7 @@ import Controls from './Controls'
|
||||
import GnoForm from 'src/components/forms/GnoForm'
|
||||
import Hairline from 'src/components/layout/Hairline'
|
||||
import { history } from 'src/store'
|
||||
import { LoadFormValues } from 'src/routes/load/container/Load'
|
||||
|
||||
const transitionProps = {
|
||||
timeout: {
|
||||
@ -20,7 +21,7 @@ const transitionProps = {
|
||||
}
|
||||
|
||||
export interface StepperPageFormProps {
|
||||
values: Record<string, string>
|
||||
values: LoadFormValues
|
||||
errors: Record<string, string>
|
||||
form: FormApi
|
||||
}
|
||||
|
@ -55,7 +55,6 @@ const mainnet: NetworkConfig = {
|
||||
WALLETS.PORTIS,
|
||||
WALLETS.TORUS,
|
||||
WALLETS.TRUST,
|
||||
WALLETS.WALLET_CONNECT,
|
||||
WALLETS.WALLET_LINK,
|
||||
WALLETS.AUTHEREUM,
|
||||
WALLETS.LATTICE,
|
||||
|
@ -36,10 +36,7 @@ class Gnosis {
|
||||
|
||||
switch (tokens.status) {
|
||||
case 'fulfilled':
|
||||
const {
|
||||
data: { results = [] },
|
||||
} = tokens.value
|
||||
collectibles.erc721Tokens = results
|
||||
collectibles.erc721Tokens = tokens.value.data || []
|
||||
break
|
||||
case 'rejected':
|
||||
console.error('no erc721 tokens for the current safe', tokens.reason)
|
||||
|
@ -5,7 +5,7 @@ import Web3 from 'web3'
|
||||
|
||||
import { ETHEREUM_NETWORK } from 'src/config/networks/network.d'
|
||||
import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses'
|
||||
import { calculateGasOf, calculateGasPrice } from 'src/logic/wallets/ethTransactions'
|
||||
import { calculateGasOf, EMPTY_DATA } from 'src/logic/wallets/ethTransactions'
|
||||
import { getWeb3, getNetworkIdFrom } from 'src/logic/wallets/getWeb3'
|
||||
import { GnosisSafe } from 'src/types/contracts/GnosisSafe.d'
|
||||
import { GnosisSafeProxyFactory } from 'src/types/contracts/GnosisSafeProxyFactory.d'
|
||||
@ -99,7 +99,7 @@ export const getSafeDeploymentTransaction = (
|
||||
safeAccounts,
|
||||
numConfirmations,
|
||||
ZERO_ADDRESS,
|
||||
'0x',
|
||||
EMPTY_DATA,
|
||||
DEFAULT_FALLBACK_HANDLER_ADDRESS,
|
||||
ZERO_ADDRESS,
|
||||
0,
|
||||
@ -120,7 +120,7 @@ export const estimateGasForDeployingSafe = async (
|
||||
safeAccounts,
|
||||
numConfirmations,
|
||||
ZERO_ADDRESS,
|
||||
'0x',
|
||||
EMPTY_DATA,
|
||||
DEFAULT_FALLBACK_HANDLER_ADDRESS,
|
||||
ZERO_ADDRESS,
|
||||
0,
|
||||
@ -130,14 +130,11 @@ export const estimateGasForDeployingSafe = async (
|
||||
const proxyFactoryData = proxyFactoryMaster.methods
|
||||
.createProxyWithNonce(safeMaster.options.address, gnosisSafeData, safeCreationSalt)
|
||||
.encodeABI()
|
||||
const gas = await calculateGasOf({
|
||||
return calculateGasOf({
|
||||
data: proxyFactoryData,
|
||||
from: userAccount,
|
||||
to: proxyFactoryMaster.options.address,
|
||||
})
|
||||
const gasPrice = await calculateGasPrice()
|
||||
|
||||
return gas * parseInt(gasPrice, 10)
|
||||
}
|
||||
|
||||
export const getGnosisSafeInstanceAt = (safeAddress: string): GnosisSafe => {
|
||||
|
59
src/logic/hooks/useEstimateSafeCreationGas.tsx
Normal file
59
src/logic/hooks/useEstimateSafeCreationGas.tsx
Normal file
@ -0,0 +1,59 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { userAccountSelector } from '../wallets/store/selectors'
|
||||
import { estimateGasForDeployingSafe } from 'src/logic/contracts/safeContracts'
|
||||
import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue'
|
||||
import { formatAmount } from 'src/logic/tokens/utils/formatAmount'
|
||||
import { getNetworkInfo } from 'src/config'
|
||||
import { calculateGasPrice } from 'src/logic/wallets/ethTransactions'
|
||||
|
||||
type EstimateSafeCreationGasProps = {
|
||||
addresses: string[]
|
||||
numOwners: number
|
||||
safeCreationSalt: number
|
||||
}
|
||||
|
||||
type SafeCreationEstimationResult = {
|
||||
gasEstimation: number // Amount of gas needed for execute or approve the transaction
|
||||
gasCostFormatted: string // Cost of gas in format '< | > 100'
|
||||
gasLimit: number // Minimum gas requited to execute the Tx
|
||||
}
|
||||
|
||||
const { nativeCoin } = getNetworkInfo()
|
||||
|
||||
export const useEstimateSafeCreationGas = ({
|
||||
addresses,
|
||||
numOwners,
|
||||
safeCreationSalt,
|
||||
}: EstimateSafeCreationGasProps): SafeCreationEstimationResult => {
|
||||
const [gasEstimation, setGasEstimation] = useState<SafeCreationEstimationResult>({
|
||||
gasEstimation: 0,
|
||||
gasCostFormatted: '< 0.001',
|
||||
gasLimit: 0,
|
||||
})
|
||||
const userAccount = useSelector(userAccountSelector)
|
||||
|
||||
useEffect(() => {
|
||||
const estimateGas = async () => {
|
||||
if (!addresses.length || !numOwners || !userAccount) {
|
||||
return
|
||||
}
|
||||
|
||||
const gasEstimation = await estimateGasForDeployingSafe(addresses, numOwners, userAccount, safeCreationSalt)
|
||||
const gasPrice = await calculateGasPrice()
|
||||
const estimatedGasCosts = gasEstimation * parseInt(gasPrice, 10)
|
||||
const gasCost = fromTokenUnit(estimatedGasCosts, nativeCoin.decimals)
|
||||
const gasCostFormatted = formatAmount(gasCost)
|
||||
|
||||
setGasEstimation({
|
||||
gasEstimation,
|
||||
gasCostFormatted,
|
||||
gasLimit: gasEstimation,
|
||||
})
|
||||
}
|
||||
|
||||
estimateGas()
|
||||
}, [numOwners, userAccount, safeCreationSalt, addresses])
|
||||
|
||||
return gasEstimation
|
||||
}
|
@ -4,8 +4,8 @@ import {
|
||||
estimateGasForTransactionApproval,
|
||||
estimateGasForTransactionCreation,
|
||||
estimateGasForTransactionExecution,
|
||||
MINIMUM_TRANSACTION_GAS,
|
||||
GAS_REQUIRED_PER_SIGNATURE,
|
||||
getFixedGasCosts,
|
||||
SAFE_TX_GAS_DATA_COST,
|
||||
} from 'src/logic/safe/transactions/gas'
|
||||
import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue'
|
||||
import { formatAmount } from 'src/logic/tokens/utils/formatAmount'
|
||||
@ -39,7 +39,11 @@ export const checkIfTxIsExecution = (
|
||||
txConfirmations?: number,
|
||||
txType?: string,
|
||||
): boolean => {
|
||||
if (threshold === 1 || sameString(txType, 'spendingLimit') || txConfirmations === threshold) {
|
||||
if (
|
||||
threshold === 1 ||
|
||||
sameString(txType, 'spendingLimit') ||
|
||||
(txConfirmations !== undefined && txConfirmations >= threshold)
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
@ -213,6 +217,8 @@ export const useEstimateTransactionGas = ({
|
||||
preApprovingOwner,
|
||||
)
|
||||
|
||||
const fixedGasCosts = getFixedGasCosts(Number(threshold))
|
||||
|
||||
try {
|
||||
const isOffChainSignature = checkIfOffChainSignatureIsPossible(isExecution, smartContractWallet, safeVersion)
|
||||
|
||||
@ -233,10 +239,10 @@ export const useEstimateTransactionGas = ({
|
||||
|
||||
const gasPrice = manualGasPrice ? web3.utils.toWei(manualGasPrice, 'gwei') : await calculateGasPrice()
|
||||
const gasPriceFormatted = web3.utils.fromWei(gasPrice, 'gwei')
|
||||
const estimatedGasCosts = gasEstimation * parseInt(gasPrice, 10)
|
||||
const estimatedGasCosts = (gasEstimation + fixedGasCosts) * parseInt(gasPrice, 10)
|
||||
const gasCost = fromTokenUnit(estimatedGasCosts, nativeCoin.decimals)
|
||||
const gasCostFormatted = formatAmount(gasCost)
|
||||
const gasLimit = (gasEstimation * 2).toString()
|
||||
const gasLimit = ((gasEstimation + fixedGasCosts) * 2).toString()
|
||||
|
||||
let txEstimationExecutionStatus = EstimationStatus.SUCCESS
|
||||
|
||||
@ -259,7 +265,7 @@ export const useEstimateTransactionGas = ({
|
||||
} catch (error) {
|
||||
console.warn(error.message)
|
||||
// We put a fixed the amount of gas to let the user try to execute the tx, but it's not accurate so it will probably fail
|
||||
const gasEstimation = MINIMUM_TRANSACTION_GAS + (threshold || 1) * GAS_REQUIRED_PER_SIGNATURE
|
||||
const gasEstimation = fixedGasCosts + SAFE_TX_GAS_DATA_COST
|
||||
const gasCost = fromTokenUnit(gasEstimation, nativeCoin.decimals)
|
||||
const gasCostFormatted = formatAmount(gasCost)
|
||||
setGasEstimation({
|
||||
|
@ -84,10 +84,11 @@ export const createTransaction = (
|
||||
|
||||
const isExecution = await shouldExecuteTransaction(safeInstance, nonce, lastTx)
|
||||
const safeVersion = await getCurrentSafeVersion(safeInstance)
|
||||
let safeTxGas
|
||||
let safeTxGas = safeTxGasArg || 0
|
||||
try {
|
||||
safeTxGas =
|
||||
safeTxGasArg || (await estimateGasForTransactionCreation(safeAddress, txData, to, valueInWei, operation))
|
||||
if (safeTxGasArg === undefined) {
|
||||
safeTxGas = await estimateGasForTransactionCreation(safeAddress, txData, to, valueInWei, operation)
|
||||
}
|
||||
} catch (error) {
|
||||
safeTxGas = safeTxGasArg || 0
|
||||
}
|
||||
|
@ -15,6 +15,10 @@ import { sameString } from 'src/utils/strings'
|
||||
export const MINIMUM_TRANSACTION_GAS = 21000
|
||||
// Estimation of gas required for each signature (aproximately 7800, roundup to 8000)
|
||||
export const GAS_REQUIRED_PER_SIGNATURE = 8000
|
||||
// We require some gas to emit the events (at least 2500) after the execution and some to perform code until the execution (500)
|
||||
// We also add 3k pay when processing safeTxGas value. We don't know this value when creating the transaction
|
||||
// Hex values different than 0 has some gas cost
|
||||
export const SAFE_TX_GAS_DATA_COST = 6000
|
||||
|
||||
// Receives the response data of the safe method requiredTxGas() and parses it to get the gas amount
|
||||
const parseRequiredTxGasResponse = (data: string): number => {
|
||||
@ -151,7 +155,7 @@ const estimateGasWithRPCCall = async (txConfig: {
|
||||
|
||||
const { error } = data
|
||||
if (error?.data) {
|
||||
return new BigNumber(data.error.data.substring(138), 16).toNumber()
|
||||
return new BigNumber(error.data.substring(138), 16).toNumber()
|
||||
}
|
||||
} catch (error) {
|
||||
console.log('Gas estimation endpoint errored: ', error.message)
|
||||
@ -178,33 +182,41 @@ const calculateMinimumGasForTransaction = async (
|
||||
additionalGasBatches: number[],
|
||||
safeAddress: string,
|
||||
estimateData: string,
|
||||
txGasEstimation: number,
|
||||
dataGasEstimation: number,
|
||||
safeTxGasEstimation: number,
|
||||
fixedGasCosts: number,
|
||||
): Promise<number> => {
|
||||
for (const additionalGas of additionalGasBatches) {
|
||||
const amountOfGasToTryTx = txGasEstimation + dataGasEstimation + fixedGasCosts + additionalGas
|
||||
console.info(`Estimating transaction creation with gas amount: ${amountOfGasToTryTx}`)
|
||||
const batchedSafeTxGas = safeTxGasEstimation + additionalGas
|
||||
// To simulate if safeTxGas is enough we need to send an estimated gasLimit that will be the sum
|
||||
// of the safeTxGasEstimation and fixedGas costs for ethereum transaction
|
||||
const gasLimit = batchedSafeTxGas + fixedGasCosts
|
||||
console.info(`Estimating safeTxGas with gas amount: ${batchedSafeTxGas}`)
|
||||
try {
|
||||
const estimation = await getGasEstimationTxResponse({
|
||||
to: safeAddress,
|
||||
from: safeAddress,
|
||||
data: estimateData,
|
||||
gasPrice: 0,
|
||||
gas: amountOfGasToTryTx,
|
||||
gas: gasLimit,
|
||||
})
|
||||
if (estimation > 0) {
|
||||
console.info(`Gas estimation successfully finished with gas amount: ${amountOfGasToTryTx}`)
|
||||
return amountOfGasToTryTx
|
||||
console.info(`Gas estimation successfully finished with gas amount: ${batchedSafeTxGas}`)
|
||||
return batchedSafeTxGas
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(`Error trying to estimate gas with amount: ${amountOfGasToTryTx}`)
|
||||
console.log(`Error trying to estimate gas with amount: ${batchedSafeTxGas}`)
|
||||
}
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
export const getFixedGasCosts = (threshold: number): number => {
|
||||
// There are some minimum gas costs to execute an Ethereum transaction
|
||||
// We add this fixed network minimum gas, the gas required to check each signature
|
||||
return MINIMUM_TRANSACTION_GAS + (threshold || 1) * GAS_REQUIRED_PER_SIGNATURE
|
||||
}
|
||||
|
||||
export const estimateGasForTransactionCreation = async (
|
||||
safeAddress: string,
|
||||
data: string,
|
||||
@ -217,32 +229,35 @@ export const estimateGasForTransactionCreation = async (
|
||||
const safeInstance = await getGnosisSafeInstanceAt(safeAddress)
|
||||
|
||||
const estimateData = safeInstance.methods.requiredTxGas(to, valueInWei, data, operation).encodeABI()
|
||||
const threshold = await safeInstance.methods.getThreshold().call()
|
||||
|
||||
const fixedGasCosts = getFixedGasCosts(Number(threshold))
|
||||
|
||||
const gasEstimationResponse = await getGasEstimationTxResponse({
|
||||
to: safeAddress,
|
||||
from: safeAddress,
|
||||
data: estimateData,
|
||||
gas: safeTxGas ? safeTxGas : undefined,
|
||||
gas: safeTxGas ? safeTxGas + fixedGasCosts : undefined,
|
||||
})
|
||||
|
||||
if (safeTxGas) {
|
||||
return gasEstimationResponse
|
||||
// When we execute we get a more precise estimate value, we log for debug purposes
|
||||
console.info('This is the smart contract minimum expected safeTxGas', gasEstimationResponse)
|
||||
// We return set safeTxGas
|
||||
return safeTxGas
|
||||
}
|
||||
|
||||
const threshold = await safeInstance.methods.getThreshold().call()
|
||||
|
||||
const dataGasEstimation = parseRequiredTxGasResponse(estimateData)
|
||||
// We add the minimum required gas for a transaction
|
||||
// TODO: This fix will be more accurate when we have a service for estimation.
|
||||
// This fix takes the safe threshold and multiplies it by GAS_REQUIRED_PER_SIGNATURE.
|
||||
const fixedGasCosts = MINIMUM_TRANSACTION_GAS + (Number(threshold) || 1) * GAS_REQUIRED_PER_SIGNATURE
|
||||
// Adding this values we should get the full safeTxGas value
|
||||
const safeTxGasEstimation = gasEstimationResponse + dataGasEstimation + SAFE_TX_GAS_DATA_COST
|
||||
// We will add gas batches in case is not enough
|
||||
const additionalGasBatches = [0, 10000, 20000, 40000, 80000, 160000, 320000, 640000, 1280000, 2560000, 5120000]
|
||||
|
||||
return await calculateMinimumGasForTransaction(
|
||||
additionalGasBatches,
|
||||
safeAddress,
|
||||
estimateData,
|
||||
gasEstimationResponse,
|
||||
dataGasEstimation,
|
||||
safeTxGasEstimation,
|
||||
fixedGasCosts,
|
||||
)
|
||||
} catch (error) {
|
||||
@ -290,6 +305,7 @@ export const estimateGasForTransactionExecution = async ({
|
||||
txRecipient,
|
||||
txAmount,
|
||||
operation,
|
||||
safeTxGas,
|
||||
)
|
||||
console.info(`Gas estimation successfully finished with gas amount: ${gasEstimation}`)
|
||||
return gasEstimation
|
||||
|
@ -16,11 +16,9 @@ export type CollectibleResult = {
|
||||
uri: string | null
|
||||
}
|
||||
|
||||
export const fetchSafeCollectibles = async (
|
||||
safeAddress: string,
|
||||
): Promise<AxiosResponse<{ results: CollectibleResult[] }>> => {
|
||||
export const fetchSafeCollectibles = async (safeAddress: string): Promise<AxiosResponse<CollectibleResult[]>> => {
|
||||
const address = checksumAddress(safeAddress)
|
||||
const url = `${getSafeServiceBaseUrl(address)}/collectibles/`
|
||||
|
||||
return axios.get<CollectibleResult[], AxiosResponse<{ results: CollectibleResult[] }>>(url)
|
||||
return axios.get<CollectibleResult[], AxiosResponse<CollectibleResult[]>>(url)
|
||||
}
|
||||
|
@ -17,8 +17,9 @@ import { getAccountsFrom } from 'src/routes/open/utils/safeDataExtractor'
|
||||
import { useStyles } from './styles'
|
||||
import { getExplorerInfo } from 'src/config'
|
||||
import { ExplorerButton } from '@gnosis.pm/safe-react-components'
|
||||
import { LoadFormValues } from 'src/routes/load/container/Load'
|
||||
|
||||
const checkIfUserAddressIsAnOwner = (values: Record<string, string>, userAddress: string): boolean => {
|
||||
const checkIfUserAddressIsAnOwner = (values: LoadFormValues, userAddress: string): boolean => {
|
||||
let isOwner = false
|
||||
|
||||
for (let i = 0; i < getNumOwnersFrom(values); i += 1) {
|
||||
@ -33,7 +34,7 @@ const checkIfUserAddressIsAnOwner = (values: Record<string, string>, userAddress
|
||||
|
||||
interface Props {
|
||||
userAddress: string
|
||||
values: Record<string, string>
|
||||
values: LoadFormValues
|
||||
}
|
||||
|
||||
const ReviewComponent = ({ userAddress, values }: Props): React.ReactElement => {
|
||||
@ -138,7 +139,7 @@ const ReviewComponent = ({ userAddress, values }: Props): React.ReactElement =>
|
||||
}
|
||||
|
||||
const Review = ({ userAddress }: { userAddress: string }) =>
|
||||
function ReviewPage(controls: React.ReactNode, { values }: { values: Record<string, string> }): React.ReactElement {
|
||||
function ReviewPage(controls: React.ReactNode, { values }: { values: LoadFormValues }): React.ReactElement {
|
||||
return (
|
||||
<>
|
||||
<OpenPaper controls={controls} padding={false}>
|
||||
|
@ -35,12 +35,24 @@ export const loadSafe = async (
|
||||
await addSafe(safeProps)
|
||||
}
|
||||
|
||||
export interface LoadFormValues {
|
||||
interface ReviewSafeCreationValues {
|
||||
confirmations: string
|
||||
name: string
|
||||
owner0Address: string
|
||||
owner0Name: string
|
||||
safeCreationSalt: number
|
||||
}
|
||||
|
||||
interface LoadForm {
|
||||
name: string
|
||||
address: string
|
||||
threshold: string
|
||||
owner0Address: string
|
||||
owner0Name: string
|
||||
}
|
||||
|
||||
export type LoadFormValues = ReviewSafeCreationValues | LoadForm
|
||||
|
||||
const Load = (): React.ReactElement => {
|
||||
const dispatch = useDispatch()
|
||||
const provider = useSelector(providerNameSelector)
|
||||
|
@ -20,7 +20,7 @@ import {
|
||||
import { WelcomeLayout } from 'src/routes/welcome/components/index'
|
||||
import { history } from 'src/store'
|
||||
import { secondary, sm } from 'src/theme/variables'
|
||||
import { networkSelector, providerNameSelector, userAccountSelector } from 'src/logic/wallets/store/selectors'
|
||||
import { providerNameSelector, userAccountSelector } from 'src/logic/wallets/store/selectors'
|
||||
import { useSelector } from 'react-redux'
|
||||
import { addressBookSelector } from 'src/logic/addressBook/store/selectors'
|
||||
import { getNameFromAddressBook } from 'src/logic/addressBook/utils'
|
||||
@ -94,7 +94,6 @@ export const Layout = (props: LayoutProps): React.ReactElement => {
|
||||
const { onCallSafeContractSubmit, safeProps } = props
|
||||
|
||||
const provider = useSelector(providerNameSelector)
|
||||
const network = useSelector(networkSelector)
|
||||
const userAccount = useSelector(userAccountSelector)
|
||||
|
||||
useEffect(() => {
|
||||
@ -107,33 +106,31 @@ export const Layout = (props: LayoutProps): React.ReactElement => {
|
||||
|
||||
const initialValues = useInitialValuesFrom(userAccount, safeProps)
|
||||
|
||||
if (!provider) {
|
||||
return <WelcomeLayout isOldMultisigMigration />
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{provider ? (
|
||||
<Block>
|
||||
<Row align="center">
|
||||
<IconButton disableRipple onClick={back} style={iconStyle}>
|
||||
<ChevronLeft />
|
||||
</IconButton>
|
||||
<Heading tag="h2" testId="create-safe-form-title">
|
||||
Create New Safe
|
||||
</Heading>
|
||||
</Row>
|
||||
<Stepper
|
||||
initialValues={initialValues}
|
||||
mutators={formMutators}
|
||||
onSubmit={onCallSafeContractSubmit}
|
||||
steps={steps}
|
||||
testId="create-safe-form"
|
||||
>
|
||||
<StepperPage component={SafeNameField} />
|
||||
<StepperPage component={SafeOwnersPage} validate={validateOwnersForm} />
|
||||
<StepperPage network={network} userAccount={userAccount} component={Review} />
|
||||
</Stepper>
|
||||
</Block>
|
||||
) : (
|
||||
<WelcomeLayout isOldMultisigMigration />
|
||||
)}
|
||||
</>
|
||||
<Block>
|
||||
<Row align="center">
|
||||
<IconButton disableRipple onClick={back} style={iconStyle}>
|
||||
<ChevronLeft />
|
||||
</IconButton>
|
||||
<Heading tag="h2" testId="create-safe-form-title">
|
||||
Create New Safe
|
||||
</Heading>
|
||||
</Row>
|
||||
<Stepper
|
||||
initialValues={initialValues}
|
||||
mutators={formMutators}
|
||||
onSubmit={onCallSafeContractSubmit}
|
||||
steps={steps}
|
||||
testId="create-safe-form"
|
||||
>
|
||||
<StepperPage component={SafeNameField} />
|
||||
<StepperPage component={SafeOwnersPage} validate={validateOwnersForm} />
|
||||
<StepperPage component={Review} />
|
||||
</Stepper>
|
||||
</Block>
|
||||
)
|
||||
}
|
||||
|
@ -1,7 +1,6 @@
|
||||
import TableContainer from '@material-ui/core/TableContainer'
|
||||
import classNames from 'classnames'
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue'
|
||||
import React, { ReactElement, useEffect, useMemo } from 'react'
|
||||
import { getExplorerInfo, getNetworkInfo } from 'src/config'
|
||||
import CopyBtn from 'src/components/CopyBtn'
|
||||
import Identicon from 'src/components/Identicon'
|
||||
@ -11,45 +10,41 @@ import Hairline from 'src/components/layout/Hairline'
|
||||
import Paragraph from 'src/components/layout/Paragraph'
|
||||
import Row from 'src/components/layout/Row'
|
||||
import OpenPaper from 'src/components/Stepper/OpenPaper'
|
||||
import { estimateGasForDeployingSafe } from 'src/logic/contracts/safeContracts'
|
||||
import { formatAmount } from 'src/logic/tokens/utils/formatAmount'
|
||||
import { getAccountsFrom, getNamesFrom, getSafeCreationSaltFrom } from 'src/routes/open/utils/safeDataExtractor'
|
||||
import {
|
||||
CreateSafeValues,
|
||||
getAccountsFrom,
|
||||
getNamesFrom,
|
||||
getSafeCreationSaltFrom,
|
||||
} from 'src/routes/open/utils/safeDataExtractor'
|
||||
|
||||
import { FIELD_CONFIRMATIONS, FIELD_NAME, getNumOwnersFrom } from '../fields'
|
||||
import { useStyles } from './styles'
|
||||
import { ExplorerButton } from '@gnosis.pm/safe-react-components'
|
||||
import { useEstimateSafeCreationGas } from 'src/logic/hooks/useEstimateSafeCreationGas'
|
||||
import { FormApi } from 'final-form'
|
||||
import { StepperPageFormProps } from 'src/components/Stepper'
|
||||
import { LoadFormValues } from 'src/routes/load/container/Load'
|
||||
|
||||
type ReviewComponentProps = {
|
||||
userAccount: string
|
||||
values: any
|
||||
values: LoadFormValues
|
||||
form: FormApi
|
||||
}
|
||||
|
||||
const { nativeCoin } = getNetworkInfo()
|
||||
|
||||
const ReviewComponent = ({ userAccount, values }: ReviewComponentProps) => {
|
||||
const ReviewComponent = ({ values, form }: ReviewComponentProps): ReactElement => {
|
||||
const classes = useStyles()
|
||||
|
||||
const [gasCosts, setGasCosts] = useState('< 0.001')
|
||||
const names = getNamesFrom(values)
|
||||
const addresses = getAccountsFrom(values)
|
||||
const addresses = useMemo(() => getAccountsFrom(values), [values])
|
||||
|
||||
const numOwners = getNumOwnersFrom(values)
|
||||
const safeCreationSalt = getSafeCreationSaltFrom(values)
|
||||
const safeCreationSalt = getSafeCreationSaltFrom(values as CreateSafeValues)
|
||||
const { gasCostFormatted, gasLimit } = useEstimateSafeCreationGas({ addresses, numOwners, safeCreationSalt })
|
||||
|
||||
useEffect(() => {
|
||||
const estimateGas = async () => {
|
||||
if (!addresses.length || !numOwners || !userAccount) {
|
||||
return
|
||||
}
|
||||
const estimatedGasCosts = (
|
||||
await estimateGasForDeployingSafe(addresses, numOwners, userAccount, safeCreationSalt)
|
||||
).toString()
|
||||
const gasCosts = fromTokenUnit(estimatedGasCosts, nativeCoin.decimals)
|
||||
const formattedGasCosts = formatAmount(gasCosts)
|
||||
setGasCosts(formattedGasCosts)
|
||||
}
|
||||
|
||||
estimateGas()
|
||||
}, [addresses, numOwners, safeCreationSalt, userAccount])
|
||||
form.mutators.setValue('gasLimit', gasLimit)
|
||||
}, [gasLimit, form.mutators])
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -135,8 +130,8 @@ const ReviewComponent = ({ userAccount, values }: ReviewComponentProps) => {
|
||||
<Row align="center" className={classes.info}>
|
||||
<Paragraph color="primary" noMargin size="md">
|
||||
You're about to create a new Safe and will have to confirm a transaction with your currently connected
|
||||
wallet. The creation will cost approximately {gasCosts} {nativeCoin.name}. The exact amount will be determined
|
||||
by your wallet.
|
||||
wallet. The creation will cost approximately {gasCostFormatted} {nativeCoin.name}. The exact amount will be
|
||||
determined by your wallet.
|
||||
</Paragraph>
|
||||
</Row>
|
||||
</>
|
||||
@ -144,7 +139,7 @@ const ReviewComponent = ({ userAccount, values }: ReviewComponentProps) => {
|
||||
}
|
||||
|
||||
export const Review = () =>
|
||||
function ReviewPage(controls, props): React.ReactElement {
|
||||
function ReviewPage(controls: React.ReactNode, props: StepperPageFormProps): React.ReactElement {
|
||||
return (
|
||||
<>
|
||||
<OpenPaper controls={controls} padding={false}>
|
||||
|
@ -4,13 +4,17 @@ export const FIELD_OWNERS = 'owners'
|
||||
export const FIELD_SAFE_NAME = 'safeName'
|
||||
export const FIELD_CREATION_PROXY_SALT = 'safeCreationSalt'
|
||||
|
||||
export const getOwnerNameBy = (index) => `owner${index}Name`
|
||||
export const getOwnerAddressBy = (index) => `owner${index}Address`
|
||||
export const getOwnerNameBy = (index: number): string => `owner${index}Name`
|
||||
export const getOwnerAddressBy = (index: number): string => `owner${index}Address`
|
||||
|
||||
export const getNumOwnersFrom = (values) => {
|
||||
const accounts = Object.keys(values)
|
||||
.sort()
|
||||
.filter((key) => /^owner\d+Address$/.test(key) && !!values[key])
|
||||
.filter((key) => {
|
||||
const res = /^owner\d+Address$/.test(key)
|
||||
|
||||
return res && !!values[key]
|
||||
})
|
||||
|
||||
return accounts.length
|
||||
}
|
||||
|
@ -6,11 +6,12 @@ import { useLocation } from 'react-router-dom'
|
||||
import { PromiEvent, TransactionReceipt } from 'web3-core'
|
||||
|
||||
import { SafeDeployment } from 'src/routes/opening'
|
||||
import { InitialValuesForm, Layout } from 'src/routes/open/components/Layout'
|
||||
import { Layout } from 'src/routes/open/components/Layout'
|
||||
import Page from 'src/components/layout/Page'
|
||||
import { getSafeDeploymentTransaction } from 'src/logic/contracts/safeContracts'
|
||||
import { checkReceiptStatus } from 'src/logic/wallets/ethTransactions'
|
||||
import {
|
||||
CreateSafeValues,
|
||||
getAccountsFrom,
|
||||
getNamesFrom,
|
||||
getOwnersFrom,
|
||||
@ -29,6 +30,8 @@ import { useAnalytics } from 'src/utils/googleAnalytics'
|
||||
|
||||
const SAFE_PENDING_CREATION_STORAGE_KEY = 'SAFE_PENDING_CREATION_STORAGE_KEY'
|
||||
|
||||
type LoadedSafeType = CreateSafeValues & { txHash: string }
|
||||
|
||||
interface SafeCreationQueryParams {
|
||||
ownerAddresses: string | string[] | null
|
||||
ownerNames: string | string[] | null
|
||||
@ -85,7 +88,7 @@ export const getSafeProps = async (
|
||||
return safeProps
|
||||
}
|
||||
|
||||
export const createSafe = (values: InitialValuesForm, userAccount: string): PromiEvent<TransactionReceipt> => {
|
||||
export const createSafe = (values: CreateSafeValues, userAccount: string): PromiEvent<TransactionReceipt> => {
|
||||
const confirmations = getThresholdFrom(values)
|
||||
const name = getSafeNameFrom(values)
|
||||
const ownersNames = getNamesFrom(values)
|
||||
@ -93,7 +96,10 @@ export const createSafe = (values: InitialValuesForm, userAccount: string): Prom
|
||||
const safeCreationSalt = getSafeCreationSaltFrom(values)
|
||||
|
||||
const deploymentTx = getSafeDeploymentTransaction(ownerAddresses, confirmations, safeCreationSalt)
|
||||
const promiEvent = deploymentTx.send({ from: userAccount })
|
||||
const promiEvent = deploymentTx.send({
|
||||
from: userAccount,
|
||||
gas: values?.gasLimit,
|
||||
})
|
||||
|
||||
promiEvent
|
||||
.once('transactionHash', (txHash) => {
|
||||
@ -155,28 +161,28 @@ const Open = (): React.ReactElement => {
|
||||
load()
|
||||
}, [])
|
||||
|
||||
const createSafeProxy = async (formValues?: InitialValuesForm) => {
|
||||
const createSafeProxy = async (formValues?: CreateSafeValues) => {
|
||||
let values = formValues
|
||||
|
||||
// save form values, used when the user rejects the TX and wants to retry
|
||||
if (formValues) {
|
||||
const copy = { ...formValues }
|
||||
if (values) {
|
||||
const copy = { ...values }
|
||||
saveToStorage(SAFE_PENDING_CREATION_STORAGE_KEY, copy)
|
||||
} else {
|
||||
values = await loadFromStorage(SAFE_PENDING_CREATION_STORAGE_KEY)
|
||||
values = (await loadFromStorage(SAFE_PENDING_CREATION_STORAGE_KEY)) as CreateSafeValues
|
||||
}
|
||||
|
||||
const promiEvent = createSafe(values as InitialValuesForm, userAccount)
|
||||
const promiEvent = createSafe(values, userAccount)
|
||||
setCreationTxPromise(promiEvent)
|
||||
setShowProgress(true)
|
||||
}
|
||||
|
||||
const onSafeCreated = async (safeAddress): Promise<void> => {
|
||||
const pendingCreation = await loadFromStorage<{ txHash: string }>(SAFE_PENDING_CREATION_STORAGE_KEY)
|
||||
const pendingCreation = await loadFromStorage<LoadedSafeType>(SAFE_PENDING_CREATION_STORAGE_KEY)
|
||||
|
||||
const name = getSafeNameFrom(pendingCreation)
|
||||
const ownersNames = getNamesFrom(pendingCreation)
|
||||
const ownerAddresses = getAccountsFrom(pendingCreation)
|
||||
const name = pendingCreation ? getSafeNameFrom(pendingCreation) : ''
|
||||
const ownersNames = getNamesFrom(pendingCreation as CreateSafeValues)
|
||||
const ownerAddresses = pendingCreation ? getAccountsFrom(pendingCreation) : []
|
||||
const safeProps = await getSafeProps(safeAddress, name, ownersNames, ownerAddresses)
|
||||
|
||||
await dispatch(addOrUpdateSafe(safeProps))
|
||||
|
@ -8,6 +8,9 @@ describe('Test JS', () => {
|
||||
owner1Address: 'bar',
|
||||
owner2Address: 'baz',
|
||||
owners: 3,
|
||||
confirmations: '0',
|
||||
name: '',
|
||||
safeCreationSalt: 0,
|
||||
}
|
||||
|
||||
expect(getAccountsFrom(safe)).toEqual(['foo', 'bar', 'baz'])
|
||||
@ -15,9 +18,15 @@ describe('Test JS', () => {
|
||||
it('return the names of owners', () => {
|
||||
const safe = {
|
||||
owner0Name: 'foo',
|
||||
owner0Address: '0x',
|
||||
owner1Name: 'bar',
|
||||
owner1Address: '0x',
|
||||
owner2Name: 'baz',
|
||||
owner2Address: '0x',
|
||||
owners: 3,
|
||||
confirmations: '0',
|
||||
name: '',
|
||||
safeCreationSalt: 0,
|
||||
}
|
||||
|
||||
expect(getNamesFrom(safe)).toEqual(['foo', 'bar', 'baz'])
|
||||
@ -31,12 +40,15 @@ describe('Test JS', () => {
|
||||
owner2Name: 'bazName',
|
||||
owner2Address: 'bazAddress',
|
||||
owners: 1,
|
||||
confirmations: '0',
|
||||
name: '',
|
||||
safeCreationSalt: 0,
|
||||
}
|
||||
|
||||
expect(getNamesFrom(safe)).toEqual(['fooName'])
|
||||
expect(getAccountsFrom(safe)).toEqual(['fooAddress'])
|
||||
expect(getNamesFrom(safe)).toEqual(['fooName', 'barName', 'bazName'])
|
||||
expect(getAccountsFrom(safe)).toEqual(['fooAddress', 'barAddress', 'bazAddress'])
|
||||
})
|
||||
it('return name and address ordered alphabetically', () => {
|
||||
it('return name and address keys ordered alphabetically', () => {
|
||||
const safe = {
|
||||
owner1Name: 'barName',
|
||||
owner1Address: 'barAddress',
|
||||
@ -45,14 +57,19 @@ describe('Test JS', () => {
|
||||
owner2Address: 'bazAddress',
|
||||
owner0Address: 'fooAddress',
|
||||
owners: 1,
|
||||
confirmations: '0',
|
||||
name: '',
|
||||
safeCreationSalt: 0,
|
||||
}
|
||||
|
||||
expect(getNamesFrom(safe)).toEqual(['fooName'])
|
||||
expect(getAccountsFrom(safe)).toEqual(['fooAddress'])
|
||||
expect(getNamesFrom(safe)).toEqual(['fooName', 'barName', 'bazName'])
|
||||
expect(getAccountsFrom(safe)).toEqual(['fooAddress', 'barAddress', 'bazAddress'])
|
||||
})
|
||||
it('return the number of required confirmations', () => {
|
||||
const safe = {
|
||||
confirmations: '1',
|
||||
name: '',
|
||||
safeCreationSalt: 0,
|
||||
}
|
||||
|
||||
expect(getThresholdFrom(safe)).toEqual(1)
|
||||
|
@ -2,31 +2,45 @@ import { List } from 'immutable'
|
||||
|
||||
import { makeOwner } from 'src/logic/safe/store/models/owner'
|
||||
import { SafeOwner } from 'src/logic/safe/store/models/safe'
|
||||
import { LoadFormValues } from 'src/routes/load/container/Load'
|
||||
import { getNumOwnersFrom } from 'src/routes/open/components/fields'
|
||||
|
||||
export const getAccountsFrom = (values) => {
|
||||
export type CreateSafeValues = {
|
||||
confirmations: string
|
||||
name: string
|
||||
owner0Address?: string
|
||||
owner0Name?: string
|
||||
safeCreationSalt: number
|
||||
gasLimit?: number
|
||||
owners?: number | string
|
||||
}
|
||||
|
||||
export const getAccountsFrom = (values: CreateSafeValues | LoadFormValues): string[] => {
|
||||
const accounts = Object.keys(values)
|
||||
.sort()
|
||||
.filter((key) => /^owner\d+Address$/.test(key))
|
||||
|
||||
return accounts.map((account) => values[account]).slice(0, values.owners)
|
||||
const numOwners = getNumOwnersFrom(values)
|
||||
return accounts.map((account) => values[account]).slice(0, numOwners)
|
||||
}
|
||||
|
||||
export const getNamesFrom = (values) => {
|
||||
export const getNamesFrom = (values: CreateSafeValues | LoadFormValues): string[] => {
|
||||
const accounts = Object.keys(values)
|
||||
.sort()
|
||||
.filter((key) => /^owner\d+Name$/.test(key))
|
||||
|
||||
return accounts.map((account) => values[account]).slice(0, values.owners)
|
||||
const numOwners = getNumOwnersFrom(values)
|
||||
return accounts.map((account) => values[account]).slice(0, numOwners)
|
||||
}
|
||||
|
||||
export const getOwnersFrom = (names, addresses): List<SafeOwner> => {
|
||||
export const getOwnersFrom = (names: string[], addresses: string[]): List<SafeOwner> => {
|
||||
const owners = names.map((name, index) => makeOwner({ name, address: addresses[index] }))
|
||||
|
||||
return List(owners)
|
||||
}
|
||||
|
||||
export const getThresholdFrom = (values) => Number(values.confirmations)
|
||||
export const getThresholdFrom = (values: CreateSafeValues): number => Number(values.confirmations)
|
||||
|
||||
export const getSafeNameFrom = (values) => values.name
|
||||
export const getSafeNameFrom = (values: CreateSafeValues): string => values.name
|
||||
|
||||
export const getSafeCreationSaltFrom = (values) => values.safeCreationSalt
|
||||
export const getSafeCreationSaltFrom = (values: CreateSafeValues): number => values.safeCreationSalt
|
||||
|
@ -69,7 +69,25 @@ export const staticAppsList: Array<StaticAppInfo> = [
|
||||
disabled: false,
|
||||
networks: [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY],
|
||||
},
|
||||
// request
|
||||
// Lido finance
|
||||
{
|
||||
url: `${process.env.REACT_APP_IPFS_GATEWAY}/Qmde8dsa9r8bB59CNGww6LRiaZABuKaJfuzvu94hFkatJC`,
|
||||
disabled: false,
|
||||
networks: [ETHEREUM_NETWORK.MAINNET],
|
||||
},
|
||||
// Mushrooms finance
|
||||
{
|
||||
url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmQs6CUbMUyKe3Sa3tU3HcnWWzsuCk8oJEk8CZKhRcJfEh`,
|
||||
disabled: false,
|
||||
networks: [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY],
|
||||
},
|
||||
// Pooltogether
|
||||
{
|
||||
url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmTa21pi77hiT1sLCGy5BeVwcyzExUSp2z7byxZukye8hr`,
|
||||
disabled: false,
|
||||
networks: [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY],
|
||||
},
|
||||
// Request
|
||||
{
|
||||
url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmTBBaiDQyGa17DJ7DdviyHbc51fTVgf6Z5PW5w2YUTkgR`,
|
||||
disabled: false,
|
||||
@ -129,12 +147,6 @@ export const staticAppsList: Array<StaticAppInfo> = [
|
||||
disabled: false,
|
||||
networks: [ETHEREUM_NETWORK.MAINNET],
|
||||
},
|
||||
// Mushrooms finance
|
||||
{
|
||||
url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmdRLqtVW9nkjZmQiryoBfpvbqXrqYT59EawZcF5WXoLCY`,
|
||||
disabled: false,
|
||||
networks: [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY],
|
||||
},
|
||||
]
|
||||
|
||||
export const getAppInfoFromOrigin = (origin: string): { url: string; name: string } | null => {
|
||||
|
@ -114,6 +114,7 @@ const ReviewSendFundsTx = ({ onClose, onPrev, tx }: ReviewTxProps): React.ReactE
|
||||
txData: data,
|
||||
txRecipient,
|
||||
txType: tx.txType,
|
||||
txAmount: txValue,
|
||||
safeTxGas: manualSafeTxGas,
|
||||
manualGasPrice,
|
||||
})
|
||||
|
@ -82,11 +82,13 @@ export const QueueTxList = ({ transactions }: QueueTxListProps): ReactElement =>
|
||||
const title = txLocation === 'queued.next' ? 'NEXT TRANSACTION' : 'QUEUE'
|
||||
|
||||
const { lastItemId, setLastItemId } = useContext(TxsInfiniteScrollContext)
|
||||
const [, lastTransactionsGroup] = transactions[transactions.length - 1]
|
||||
const lastTransaction = lastTransactionsGroup[lastTransactionsGroup.length - 1]
|
||||
if (transactions.length) {
|
||||
const [, lastTransactionsGroup] = transactions[transactions.length - 1]
|
||||
const lastTransaction = lastTransactionsGroup[lastTransactionsGroup.length - 1]
|
||||
|
||||
if (txLocation === 'queued.queued' && !sameString(lastItemId, lastTransaction.id)) {
|
||||
setLastItemId(lastTransaction.id)
|
||||
if (txLocation === 'queued.queued' && !sameString(lastItemId, lastTransaction.id)) {
|
||||
setLastItemId(lastTransaction.id)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
|
@ -52,7 +52,7 @@ export const TxOwners = ({ detailedExecutionInfo }: TxOwnersProps): ReactElement
|
||||
</div>
|
||||
</OwnerListItem>
|
||||
))}
|
||||
{confirmationsNeeded === 0 ? (
|
||||
{confirmationsNeeded <= 0 ? (
|
||||
<OwnerListItem>
|
||||
<span className="icon">
|
||||
<StyledImg alt="" src={detailedExecutionInfo.executor ? CheckCircleGreen : TransactionListActive} />
|
||||
|
@ -29,6 +29,7 @@ export const usePagedHistoryTransactions = (): PagedTransactions => {
|
||||
|
||||
if (!results) {
|
||||
setHasMore(false)
|
||||
setIsLoading(false)
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -13,7 +13,7 @@ import { AppReduxState } from 'src/store'
|
||||
|
||||
export const isThresholdReached = (executionInfo: ExecutionInfo): boolean => {
|
||||
const { confirmationsSubmitted, confirmationsRequired } = executionInfo
|
||||
return confirmationsSubmitted === confirmationsRequired
|
||||
return confirmationsSubmitted >= confirmationsRequired
|
||||
}
|
||||
|
||||
export type TransactionActions = {
|
||||
|
Loading…
x
Reference in New Issue
Block a user