Release v3.1.0 (#1953)

* Added help center article link to step 2

* Removed deprecated wallets (#1922)

* Bump new onboardjs version

* Fix notification re-appears (#1925)

* (Fix) - #1775 Nonce of cancel transaction calculation (#1886)

* Fix how the nonce of the cancel transaction is calculated

* make use useState to handle nonce state

* fix to prevent "0" being treated as undefined

* (Fix) - #1707 Cannot use larger numbers in contract interaction (#1863)

* Remove withStyles from TextAreaField and fix name

* Remove any type in handleSubmit from contractInteraction review

* Parses the bignumber value to string

* Added link to docs for custom app modal

* Fix styles to match design

* Fix nonce 0 check (#1941)

* Use tooltip from SRC (#1888)

* Upgrade safe-react-components to latest version

* fix New Transaction button

* Increase the date/time tooltip size

* Feature: Add Mushrooms finance app (#1893)

* add mushrooms finance app

* Migrate to GitHub actions (#1924)

* Add Github action for each network environment

* Move deploy scripts to new folder

* Adapt deploy scripts to Github actions

* Run coveralls only if tests succeed

* Upload sentry source map

* Add Production flag for tagged builds

* Use coveralls Github Action

* Add debug steps to all networks for first release test

* Avoid to remove the current loaded safe data if the batch request fail (#1847)

* Fix gas estimation (#1944)

* Fix gas estimation for threshold > 4

* Update gas estimation to be more precise

* Add threshold gas costs on transaction creation estimation

Co-authored-by: Daniel Sanchez <daniel.sanchez@gnosis.pm>

* update ipfs hash for wc app (#1954)

* Update endpoint to use new name (#1955)

* (Fix) Transactions infinite scroll (#1931)

* install `react-intersection-observer` dependency

- also, remove `react-infinite-scroll-component`

* refactor `InfiniteScroll` to be used with `react-intersection-observer`

* build an infinite scroll wrapper for transactions based on `InfiniteScroll`

* recover `TxsInfiniteScrollContext` information to identify the last item in a list

- a new component was created for History transactions: `HistoryTransactions` as a wrapper

* refactor lists to use `TxsInfiniteScrollContext` and identify the last item in the list

* allow to pass config to the InfiniteScroll component

 - also changed default bottom margin so the txs loading starts a bit earlier

* fix memory consumption issue based on nft retrieval/update data

* delay `lastItemId` set to next tick, to prevent multiple updates during the same render phase

* Set triggerOnce to infinitescroll

* Fix gas estimation (#1959)

* Fix type of fetchSafeCollectibles (#1971)

* Fix transaction list infinite loading (#1973)

* Check transaction list before trying to assing guard element to infinite scroll in pending list (#1972)

* Fix execute if collected signs is > to threshold (#1968)

* Feature: Add lido finance app (#1960)

* Add pooltogether safe app (#1946)

* fix isExecute in useEstimateTransactionGas (#1981)

* Set V3.1.0

Co-authored-by: Mati Dastugue <mdastugu@amazon.com>
Co-authored-by: Mati Dastugue <matias.dastugue@altoros.com>
Co-authored-by: nicolas <nicosampler@users.noreply.github.com>
Co-authored-by: Agustin Pane <agustin.pane@gmail.com>
Co-authored-by: Fernando <fernando.greco@gmail.com>
Co-authored-by: Agustín Longoni <agustin.longoni@altoros.com>
Co-authored-by: Mikhail Mikheev <mmvsha73@gmail.com>
Co-authored-by: nicosampler <nf.dominguez.87@gmail.com>
This commit is contained in:
Daniel Sanchez 2021-03-03 10:17:40 +01:00 committed by GitHub
parent a4e3c82ca2
commit 4f9fd13285
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 3717 additions and 2210 deletions

141
.github/workflows/deploy-ewc.yml vendored Normal file
View File

@ -0,0 +1,141 @@
name: Deploy to EWC network
# Run on pushes to master
on:
push:
branches:
- master
# Launches build when release is published
release:
types: [published]
env:
REPO_NAME_ALPHANUMERIC: safereact
REACT_APP_NETWORK: 'ewc'
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 }}
REACT_APP_GNOSIS_APPS_URL: ${{ secrets.REACT_APP_GNOSIS_APPS_URL_PROD }}
jobs:
debug:
name: Debug
runs-on: ubuntu-latest
steps:
- name: Dump env
run: env | sort
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
deploy:
name: Deployment
runs-on: ubuntu-latest
steps:
- name: Remove broken apt repos [Ubuntu]
if: matrix.os == 'ubuntu-latest'
run: |
for apt_file in `grep -lr microsoft /etc/apt/sources.list.d/`; do sudo rm $apt_file; done
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 14
- uses: actions/cache@v2
with:
path: '**/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- run: |
sudo apt-get update
sudo apt-get -y install python3-pip python3-dev libusb-1.0-0-dev libudev-dev
pip install awscli --upgrade --user
# Due to some dependencies yarn may randomly throw an error about invalid cache
# This approach is taken from https://github.com/yarnpkg/yarn/issues/7212#issuecomment-506155894 to fix the issue
# Another approach is to install with flag --network-concurrency 1, but this will make the installation pretty slow (default value is 8)
mkdir .yarncache
yarn install --cache-folder ./.yarncache --frozen-lockfile
rm -rf .yarncache
yarn cache clean
# Set production flag
- name: Set production flag for tag build
run: echo "REACT_APP_ENV='production'" >> $GITHUB_ENV
if: startsWith(github.ref, 'refs/tags/v')
- name: Build ${{ env.REACT_APP_NETWORK }} app
run: yarn build
env:
PUBLIC_URL: './'
REACT_APP_FORTMATIC_KEY: ${{ secrets.REACT_APP_FORTMATIC_KEY }}
REACT_APP_INFURA_TOKEN: ${{ secrets.REACT_APP_INFURA_TOKEN }}
REACT_APP_PORTIS_ID: ${{ secrets.REACT_APP_PORTIS_ID }}
REACT_APP_INTERCOM_ID: ${{ secrets.REACT_APP_INTERCOM_ID }}
REACT_APP_IPFS_GATEWAY: ${{ secrets.REACT_APP_IPFS_GATEWAY }}
# Script to deploy Pull Requests
- run: bash ./scripts/github/deploy_pull_request.sh
if: success() && github.event.number
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 }}
- name: 'PRaul: Comment PR with app URLs'
uses: mshick/add-pr-comment@v1
with:
message: |
* [Safe Multisig app ${{ env.REACT_APP_NETWORK }}](${{ env.REVIEW_FEATURE_URL }}/${{ env.REACT_APP_NETWORK }}/app/)
repo-token: ${{ secrets.GITHUB_TOKEN }}
repo-token-user-login: 'github-actions[bot]'
allow-repeats: true
if: success() && github.event.number
env:
REVIEW_FEATURE_URL: https://pr${{ github.event.number }}--${{ env.REPO_NAME_ALPHANUMERIC }}.review.gnosisdev.com
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_DEFAULT_REGION }}
# Script to deploy to development environment
# EWC build is never created in development branch
# 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
# Script to upload release files
- 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')
env:
PROD_DEPLOYMENT_HOOK_TOKEN: ${{ secrets.PROD_DEPLOYMENT_HOOK_TOKEN }}
PROD_DEPLOYMENT_HOOK_URL: ${{ secrets.PROD_DEPLOYMENT_HOOK_URL }}
VERSION_TAG: ${{ github.event.release.tag_name }}
# Upload Sentry source maps when sending to staging or production
- run: yarn sentry-upload-sourcemaps
if: success() && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v'))
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG}}
SENTRY_PROJECT: gnosis-safe-multisig-${{ env.REACT_APP_NETWORK }}

123
.github/workflows/deploy-mainnet.yml vendored Normal file
View File

@ -0,0 +1,123 @@
name: Deploy to Mainnet network
# Run on pushes to master
on:
push:
branches:
- master
# Launches build when release is published
release:
types: [published]
env:
REPO_NAME_ALPHANUMERIC: safereact
REACT_APP_NETWORK: 'mainnet'
STAGING_BUCKET_NAME: ${{ secrets.STAGING_MAINNET_BUCKET_NAME }}
REACT_APP_SENTRY_DSN: ${{ secrets.SENTRY_DSN_MAINNET }}
REACT_APP_GOOGLE_ANALYTICS: ${{ secrets.REACT_APP_GOOGLE_ANALYTICS_ID_MAINNET }}
REACT_APP_GNOSIS_APPS_URL: ${{ secrets.REACT_APP_GNOSIS_APPS_URL_PROD }}
jobs:
debug:
name: Debug
runs-on: ubuntu-latest
steps:
- name: Dump env
run: env | sort
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
deploy:
name: Deployment
runs-on: ubuntu-latest
steps:
- name: Remove broken apt repos [Ubuntu]
if: matrix.os == 'ubuntu-latest'
run: |
for apt_file in `grep -lr microsoft /etc/apt/sources.list.d/`; do sudo rm $apt_file; done
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 14
- uses: actions/cache@v2
with:
path: '**/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- run: |
sudo apt-get update
sudo apt-get -y install python3-pip python3-dev libusb-1.0-0-dev libudev-dev
pip install awscli --upgrade --user
# Due to some dependencies yarn may randomly throw an error about invalid cache
# This approach is taken from https://github.com/yarnpkg/yarn/issues/7212#issuecomment-506155894 to fix the issue
# Another approach is to install with flag --network-concurrency 1, but this will make the installation pretty slow (default value is 8)
mkdir .yarncache
yarn install --cache-folder ./.yarncache --frozen-lockfile
rm -rf .yarncache
yarn cache clean
# Set production flag
- name: Set production flag for tag build
run: echo "REACT_APP_ENV='production'" >> $GITHUB_ENV
if: startsWith(github.ref, 'refs/tags/v')
- name: Build ${{ env.REACT_APP_NETWORK }} app
run: yarn build
env:
PUBLIC_URL: './'
REACT_APP_FORTMATIC_KEY: ${{ secrets.REACT_APP_FORTMATIC_KEY }}
REACT_APP_INFURA_TOKEN: ${{ secrets.REACT_APP_INFURA_TOKEN }}
REACT_APP_PORTIS_ID: ${{ secrets.REACT_APP_PORTIS_ID }}
REACT_APP_INTERCOM_ID: ${{ secrets.REACT_APP_INTERCOM_ID }}
REACT_APP_IPFS_GATEWAY: ${{ secrets.REACT_APP_IPFS_GATEWAY }}
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_DEFAULT_REGION }}
# Script to deploy Pull Requests
# Mainnet build is never created in Pull Requests
# Script to deploy to development environment
# Mainnet build is never created in development branch
# 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
# Script to upload release files
- 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')
env:
PROD_DEPLOYMENT_HOOK_TOKEN: ${{ secrets.PROD_DEPLOYMENT_HOOK_TOKEN }}
PROD_DEPLOYMENT_HOOK_URL: ${{ secrets.PROD_DEPLOYMENT_HOOK_URL }}
VERSION_TAG: ${{ github.event.release.tag_name }}
# Upload Sentry source maps when sending to staging or production
- run: yarn sentry-upload-sourcemaps
if: success() && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v'))
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG}}
SENTRY_PROJECT: gnosis-safe-multisig-${{ env.REACT_APP_NETWORK }}

148
.github/workflows/deploy-rinkeby.yml vendored Normal file
View File

@ -0,0 +1,148 @@
name: Deploy to Rinkeby network
# Run on pushes to dev/master or PR
on:
# Pull request hook without any config. Launches for every pull request
pull_request:
# Launches for pushes to master or development
push:
branches:
- master
- development
# Launches build when release is published
release:
types: [published]
workflow_dispatch:
env:
REPO_NAME_ALPHANUMERIC: safereact
REACT_APP_NETWORK: 'rinkeby'
STAGING_BUCKET_NAME: ${{ secrets.STAGING_RINKEBY_BUCKET_NAME }}
REACT_APP_SENTRY_DSN: ${{ secrets.SENTRY_DSN_RINKEBY }}
REACT_APP_GOOGLE_ANALYTICS: ${{ secrets.REACT_APP_GOOGLE_ANALYTICS_ID_RINKEBY }}
REACT_APP_GNOSIS_APPS_URL: ${{ secrets.REACT_APP_GNOSIS_APPS_URL_PROD }}
jobs:
debug:
name: Debug
runs-on: ubuntu-latest
steps:
- name: Dump env
run: env | sort
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
deploy:
name: Deployment
runs-on: ubuntu-latest
steps:
- name: Remove broken apt repos [Ubuntu]
if: matrix.os == 'ubuntu-latest'
run: |
for apt_file in `grep -lr microsoft /etc/apt/sources.list.d/`; do sudo rm $apt_file; done
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 14
- uses: actions/cache@v2
with:
path: '**/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- run: |
sudo apt-get update
sudo apt-get -y install python3-pip python3-dev libusb-1.0-0-dev libudev-dev
pip install awscli --upgrade --user
# Due to some dependencies yarn may randomly throw an error about invalid cache
# This approach is taken from https://github.com/yarnpkg/yarn/issues/7212#issuecomment-506155894 to fix the issue
# Another approach is to install with flag --network-concurrency 1, but this will make the installation pretty slow (default value is 8)
mkdir .yarncache
yarn install --cache-folder ./.yarncache --frozen-lockfile
rm -rf .yarncache
yarn cache clean
# Set production flag
- name: Set production flag for tag build
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 }}
run: yarn build
env:
PUBLIC_URL: './'
REACT_APP_FORTMATIC_KEY: ${{ secrets.REACT_APP_FORTMATIC_KEY }}
REACT_APP_INFURA_TOKEN: ${{ secrets.REACT_APP_INFURA_TOKEN }}
REACT_APP_PORTIS_ID: ${{ secrets.REACT_APP_PORTIS_ID }}
REACT_APP_INTERCOM_ID: ${{ secrets.REACT_APP_INTERCOM_ID }}
REACT_APP_IPFS_GATEWAY: ${{ secrets.REACT_APP_IPFS_GATEWAY }}
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_DEFAULT_REGION }}
# Script to deploy Pull Requests
- run: bash ./scripts/github/deploy_pull_request.sh
if: success() && github.event.number
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 }}
TRAVIS_TAG: ${{ github.event.release.tag_name }}
- name: 'PRaul: Comment PR with app URLs'
uses: mshick/add-pr-comment@v1
with:
message: |
* [Safe Multisig app ${{ env.REACT_APP_NETWORK }}](${{ env.REVIEW_FEATURE_URL }}/${{ env.REACT_APP_NETWORK }}/app/)
repo-token: ${{ secrets.GITHUB_TOKEN }}
repo-token-user-login: 'github-actions[bot]'
allow-repeats: true
if: success() && github.event.number
env:
REVIEW_FEATURE_URL: https://pr${{ github.event.number }}--${{ env.REPO_NAME_ALPHANUMERIC }}.review.gnosisdev.com
# Script to deploy to development environment
- name: 'Deploy to S3: Develop'
if: github.ref == 'refs/heads/development'
run: aws s3 sync build s3://${{ secrets.AWS_DEV_BUCKET_NAME }}/app --delete
# 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
# Script to upload release files
- 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')
env:
PROD_DEPLOYMENT_HOOK_TOKEN: ${{ secrets.PROD_DEPLOYMENT_HOOK_TOKEN }}
PROD_DEPLOYMENT_HOOK_URL: ${{ secrets.PROD_DEPLOYMENT_HOOK_URL }}
VERSION_TAG: ${{ github.event.release.tag_name }}
# Upload Sentry source maps when sending to staging or production
- run: yarn sentry-upload-sourcemaps
if: success() && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v'))
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG}}
SENTRY_PROJECT: gnosis-safe-multisig-${{ env.REACT_APP_NETWORK }}

145
.github/workflows/deploy-volta.yml vendored Normal file
View File

@ -0,0 +1,145 @@
name: Deploy to Volta network
# Run on pushes to master or PRs to master
on:
push:
branches:
- master
pull_request:
branches:
- master
# Launches build when release is published
release:
types: [published]
env:
REPO_NAME_ALPHANUMERIC: safereact
REACT_APP_NETWORK: 'volta'
STAGING_BUCKET_NAME: ${{ secrets.STAGING_VOLTA_BUCKET_NAME }}
REACT_APP_SENTRY_DSN: ${{ secrets.SENTRY_DSN_VOLTA }}
REACT_APP_GOOGLE_ANALYTICS: ${{ secrets.REACT_APP_GOOGLE_ANALYTICS_ID_VOLTA }}
REACT_APP_GNOSIS_APPS_URL: ${{ secrets.REACT_APP_GNOSIS_APPS_URL_PROD }}
jobs:
debug:
name: Debug
runs-on: ubuntu-latest
steps:
- name: Dump env
run: env | sort
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
deploy:
name: Deployment
runs-on: ubuntu-latest
steps:
- name: Remove broken apt repos [Ubuntu]
if: matrix.os == 'ubuntu-latest'
run: |
for apt_file in `grep -lr microsoft /etc/apt/sources.list.d/`; do sudo rm $apt_file; done
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 14
- uses: actions/cache@v2
with:
path: '**/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- run: |
sudo apt-get update
sudo apt-get -y install python3-pip python3-dev libusb-1.0-0-dev libudev-dev
pip install awscli --upgrade --user
# Due to some dependencies yarn may randomly throw an error about invalid cache
# This approach is taken from https://github.com/yarnpkg/yarn/issues/7212#issuecomment-506155894 to fix the issue
# Another approach is to install with flag --network-concurrency 1, but this will make the installation pretty slow (default value is 8)
mkdir .yarncache
yarn install --cache-folder ./.yarncache --frozen-lockfile
rm -rf .yarncache
yarn cache clean
# Set production flag
- name: Set production flag for tag build
run: echo "REACT_APP_ENV='production'" >> $GITHUB_ENV
if: startsWith(github.ref, 'refs/tags/v')
- name: Build ${{ env.REACT_APP_NETWORK }} app
run: yarn build
env:
PUBLIC_URL: './'
REACT_APP_FORTMATIC_KEY: ${{ secrets.REACT_APP_FORTMATIC_KEY }}
REACT_APP_INFURA_TOKEN: ${{ secrets.REACT_APP_INFURA_TOKEN }}
REACT_APP_PORTIS_ID: ${{ secrets.REACT_APP_PORTIS_ID }}
REACT_APP_INTERCOM_ID: ${{ secrets.REACT_APP_INTERCOM_ID }}
REACT_APP_IPFS_GATEWAY: ${{ secrets.REACT_APP_IPFS_GATEWAY }}
# Script to deploy Pull Requests
- run: bash ./scripts/github/deploy_pull_request.sh
if: success() && github.event.number
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 }}
- name: 'PRaul: Comment PR with app URLs'
uses: mshick/add-pr-comment@v1
with:
message: |
* [Safe Multisig app ${{ env.REACT_APP_NETWORK }}](${{ env.REVIEW_FEATURE_URL }}/${{ env.REACT_APP_NETWORK }}/app/)
repo-token: ${{ secrets.GITHUB_TOKEN }}
repo-token-user-login: 'github-actions[bot]'
allow-repeats: true
if: success() && github.event.number
env:
REVIEW_FEATURE_URL: https://pr${{ github.event.number }}--${{ env.REPO_NAME_ALPHANUMERIC }}.review.gnosisdev.com
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_DEFAULT_REGION }}
# Script to deploy to development environment
# Volta build is never created in development branch
# 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
# Script to upload release files
- 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')
env:
PROD_DEPLOYMENT_HOOK_TOKEN: ${{ secrets.PROD_DEPLOYMENT_HOOK_TOKEN }}
PROD_DEPLOYMENT_HOOK_URL: ${{ secrets.PROD_DEPLOYMENT_HOOK_URL }}
VERSION_TAG: ${{ github.event.release.tag_name }}
# Upload Sentry source maps when sending to staging or production
- run: yarn sentry-upload-sourcemaps
if: success() && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v'))
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG}}
SENTRY_PROJECT: gnosis-safe-multisig-${{ env.REACT_APP_NETWORK }}

144
.github/workflows/deploy-xdai.yml vendored Normal file
View File

@ -0,0 +1,144 @@
name: Deploy to xDai network
# Run on pushes to master or PRs to master
on:
push:
branches:
- master
pull_request:
branches:
- master
# Launches build when release is published
release:
types: [published]
env:
REPO_NAME_ALPHANUMERIC: safereact
REACT_APP_NETWORK: 'xdai'
STAGING_BUCKET_NAME: ${{ secrets.STAGING_XDAI_BUCKET_NAME }}
REACT_APP_SENTRY_DSN: ${{ secrets.SENTRY_DSN_XDAI }}
REACT_APP_GOOGLE_ANALYTICS: ${{ secrets.REACT_APP_GOOGLE_ANALYTICS_ID_XDAI }}
REACT_APP_GNOSIS_APPS_URL: ${{ secrets.REACT_APP_GNOSIS_APPS_URL_PROD }}
jobs:
debug:
name: Debug
runs-on: ubuntu-latest
steps:
- name: Dump env
run: env | sort
- name: Dump GitHub context
env:
GITHUB_CONTEXT: ${{ toJson(github) }}
run: echo "$GITHUB_CONTEXT"
deploy:
name: Deployment
runs-on: ubuntu-latest
steps:
- name: Remove broken apt repos [Ubuntu]
if: matrix.os == 'ubuntu-latest'
run: |
for apt_file in `grep -lr microsoft /etc/apt/sources.list.d/`; do sudo rm $apt_file; done
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 14
- uses: actions/cache@v2
with:
path: '**/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- run: |
sudo apt-get update
sudo apt-get -y install python3-pip python3-dev libusb-1.0-0-dev libudev-dev
pip install awscli --upgrade --user
# Due to some dependencies yarn may randomly throw an error about invalid cache
# This approach is taken from https://github.com/yarnpkg/yarn/issues/7212#issuecomment-506155894 to fix the issue
# Another approach is to install with flag --network-concurrency 1, but this will make the installation pretty slow (default value is 8)
mkdir .yarncache
yarn install --cache-folder ./.yarncache --frozen-lockfile
rm -rf .yarncache
yarn cache clean
# Set production flag
- name: Set production flag for tag build
run: echo "REACT_APP_ENV='production'" >> $GITHUB_ENV
if: startsWith(github.ref, 'refs/tags/v')
- name: Build ${{ env.REACT_APP_NETWORK }} app
run: yarn build
env:
PUBLIC_URL: './'
REACT_APP_FORTMATIC_KEY: ${{ secrets.REACT_APP_FORTMATIC_KEY }}
REACT_APP_INFURA_TOKEN: ${{ secrets.REACT_APP_INFURA_TOKEN }}
REACT_APP_PORTIS_ID: ${{ secrets.REACT_APP_PORTIS_ID }}
REACT_APP_INTERCOM_ID: ${{ secrets.REACT_APP_INTERCOM_ID }}
REACT_APP_IPFS_GATEWAY: ${{ secrets.REACT_APP_IPFS_GATEWAY }}
# Script to deploy Pull Requests
- run: bash ./scripts/github/deploy_pull_request.sh
if: success() && github.event.number
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 }}
- name: 'PRaul: Comment PR with app URLs'
uses: mshick/add-pr-comment@v1
with:
message: |
* [Safe Multisig app ${{ env.REACT_APP_NETWORK }}](${{ env.REVIEW_FEATURE_URL }}/${{ env.REACT_APP_NETWORK }}/app/)
repo-token: ${{ secrets.GITHUB_TOKEN }}
repo-token-user-login: 'github-actions[bot]'
allow-repeats: true
if: success() && github.event.number
env:
REVIEW_FEATURE_URL: https://pr${{ github.event.number }}--${{ env.REPO_NAME_ALPHANUMERIC }}.review.gnosisdev.com
- name: Configure AWS credentials
uses: aws-actions/configure-aws-credentials@v1
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ secrets.AWS_DEFAULT_REGION }}
# Script to deploy to development environment
# xDai build is never created in development branch
# 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
# Script to upload release files
- 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')
env:
PROD_DEPLOYMENT_HOOK_TOKEN: ${{ secrets.PROD_DEPLOYMENT_HOOK_TOKEN }}
PROD_DEPLOYMENT_HOOK_URL: ${{ secrets.PROD_DEPLOYMENT_HOOK_URL }}
VERSION_TAG: ${{ github.event.release.tag_name }}
# Upload Sentry source maps when sending to staging or production
- run: yarn sentry-upload-sourcemaps
if: success() && (github.ref == 'refs/heads/master' || startsWith(github.ref, 'refs/tags/v'))
env:
SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }}
SENTRY_ORG: ${{ secrets.SENTRY_ORG}}
SENTRY_PROJECT: gnosis-safe-multisig-${{ env.REACT_APP_NETWORK }}

View File

@ -1,4 +1,4 @@
name: Build/Release Desktop app
name: Build/Release Mainnet desktop app
# this will help you specify where to run
on:
@ -6,10 +6,9 @@ on:
env:
REACT_APP_FORTMATIC_KEY: ${{ secrets.REACT_APP_FORTMATIC_KEY }}
REACT_APP_GOOGLE_ANALYTICS_ID_MAINNET: ${{ secrets.REACT_APP_GOOGLE_ANALYTICS_ID_MAINNET }}
REACT_APP_INFURA_TOKEN: ${{ secrets.REACT_APP_INFURA_TOKEN }}
REACT_APP_PORTIS_ID: ${{ secrets.REACT_APP_PORTIS_ID }}
REACT_APP_GNOSIS_APPS_URL: ${{ secrets.REACT_APP_GNOSIS_APPS_URL }}
REACT_APP_GNOSIS_APPS_URL: ${{ secrets.REACT_APP_GNOSIS_APPS_URL_PROD }}
REACT_APP_INTERCOM_ID: ${{ secrets.REACT_APP_INTERCOM_ID }}
REACT_APP_IPFS_GATEWAY: ${{ secrets.REACT_APP_IPFS_GATEWAY }}
jobs:
@ -26,18 +25,10 @@ jobs:
- name: Check out Git repository
uses: actions/checkout@v2
# Add cache for yarn directory
# - name: Get yarn cache directory path
# id: yarn-cache-dir-path
# run: echo "::set-output name=dir::$(yarn cache dir)"
# - uses: actions/cache@v2
# id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
# with:
# path: ${{ steps.yarn-cache-dir-path.outputs.dir }}
# key: ${{ runner.os }}-yarn-${{ hashFiles('**/yarn.lock') }}
# restore-keys: |
# ${{ runner.os }}-yarn-
- uses: actions/cache@v2
with:
path: '**/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- name: Patch node gyp on windows to support Visual Studio 2019
if: startsWith(matrix.os, 'windows')
@ -54,7 +45,15 @@ jobs:
uses: actions/setup-node@v2
with:
node-version: 14
- run: yarn install --frozen-lockfile --network-concurrency 1
- run: |
# Due to some dependencies yarn may randomly throw an error about invalid cache
# This approach is taken from https://github.com/yarnpkg/yarn/issues/7212#issuecomment-506155894 to fix the issue
# Another approach is to install with flag --network-concurrency 1, but this will make the installation pretty slow (default value is 8)
mkdir .yarncache
yarn install --cache-folder ./.yarncache --frozen-lockfile
rm -rf .yarncache
yarn cache clean
- name: Build/Release Desktop App
env:
# macOS notarization API key

43
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,43 @@
name: Run app tests
on:
pull_request:
push:
branches:
- master
- development
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Setup Node.js
uses: actions/setup-node@v2
with:
node-version: 14
- uses: actions/cache@v2
with:
path: '**/node_modules'
key: ${{ runner.os }}-modules-${{ hashFiles('**/yarn.lock') }}
- run: |
sudo apt-get update
sudo apt-get -y install python3-pip python3-dev libusb-1.0-0-dev libudev-dev
pip install awscli --upgrade --user
# Due to some dependencies yarn may randomly throw an error about invalid cache
# This approach is taken from https://github.com/yarnpkg/yarn/issues/7212#issuecomment-506155894 to fix the issue
# Another approach is to install with flag --network-concurrency 1, but this will make the installation pretty slow (default value is 8)
mkdir .yarncache
yarn install --cache-folder ./.yarncache --frozen-lockfile
rm -rf .yarncache
yarn cache clean
- run: yarn test:coverage
- name: Upload coverage to Coveralls
uses: coverallsapp/github-action@master
with:
github-token: ${{ secrets.GITHUB_TOKEN }}
if: success()

View File

@ -1,4 +1,5 @@
if: (branch = development) OR (branch = master) OR (type = pull_request) OR (tag IS present)
# if: (branch = development) OR (branch = master) OR (type = pull_request) OR (tag IS present)
if: (branch = master) OR (tag IS present)
dist: focal
language: node_js
node_js:
@ -6,42 +7,43 @@ node_js:
os:
- linux
matrix:
include:
- env:
- REACT_APP_NETWORK='mainnet'
- STAGING_BUCKET_NAME=${STAGING_MAINNET_BUCKET_NAME}
- REACT_APP_SENTRY_DSN=${SENTRY_DSN_MAINNET}
- SENTRY_PROJECT=${SENTRY_PROJECT_MAINNET}
- REACT_APP_GOOGLE_ANALYTICS=${REACT_APP_GOOGLE_ANALYTICS_ID_MAINNET}
- REACT_APP_GNOSIS_APPS_URL=${REACT_APP_GNOSIS_APPS_URL_PROD}
if: (branch = master AND NOT type = pull_request) OR tag IS present
- env:
- REACT_APP_NETWORK='rinkeby'
- REACT_APP_SENTRY_DSN=${SENTRY_DSN_RINKEBY}
- SENTRY_PROJECT=${SENTRY_PROJECT_RINKEBY}
- REACT_APP_GOOGLE_ANALYTICS=${REACT_APP_GOOGLE_ANALYTICS_ID_RINKEBY}
- REACT_APP_GNOSIS_APPS_URL=${REACT_APP_GNOSIS_APPS_URL_STAGING}
- env:
- REACT_APP_NETWORK='xdai'
- STAGING_BUCKET_NAME=${STAGING_XDAI_BUCKET_NAME}
- REACT_APP_SENTRY_DSN=${SENTRY_DSN_XDAI}
- SENTRY_PROJECT=${SENTRY_PROJECT_XDAI}
- REACT_APP_GOOGLE_ANALYTICS=${REACT_APP_GOOGLE_ANALYTICS_ID_XDAI}
if: (branch = master) OR tag IS present
- env:
- REACT_APP_NETWORK='volta'
- STAGING_BUCKET_NAME=${STAGING_VOLTA_BUCKET_NAME}
- REACT_APP_SENTRY_DSN=${SENTRY_DSN_VOLTA}
- SENTRY_PROJECT=${SENTRY_PROJECT_VOLTA}
- REACT_APP_GOOGLE_ANALYTICS=${REACT_APP_GOOGLE_ANALYTICS_ID_VOLTA}
if: (branch = master) OR tag IS present
- env:
- REACT_APP_NETWORK='energy_web_chain'
- STAGING_BUCKET_NAME=${STAGING_EWC_BUCKET_NAME}
- REACT_APP_SENTRY_DSN=${SENTRY_DSN_EWC}
- SENTRY_PROJECT=${SENTRY_PROJECT_EWC}
- REACT_APP_GOOGLE_ANALYTICS=${REACT_APP_GOOGLE_ANALYTICS_ID_EWC}
if: (branch = master AND NOT type = pull_request) OR tag IS present
# include:
# - env:
# - REACT_APP_NETWORK='mainnet'
# - STAGING_BUCKET_NAME=${STAGING_MAINNET_BUCKET_NAME}
# - REACT_APP_SENTRY_DSN=${SENTRY_DSN_MAINNET}
# - SENTRY_PROJECT=${SENTRY_PROJECT_MAINNET}
# - REACT_APP_GOOGLE_ANALYTICS=${REACT_APP_GOOGLE_ANALYTICS_ID_MAINNET}
# - REACT_APP_GNOSIS_APPS_URL=${REACT_APP_GNOSIS_APPS_URL_PROD}
# if: (branch = master AND NOT type = pull_request) OR tag IS present
# - env:
# - REACT_APP_NETWORK='rinkeby'
# - REACT_APP_SENTRY_DSN=${SENTRY_DSN_RINKEBY}
# - SENTRY_PROJECT=${SENTRY_PROJECT_RINKEBY}
# - REACT_APP_GOOGLE_ANALYTICS=${REACT_APP_GOOGLE_ANALYTICS_ID_RINKEBY}
# - REACT_APP_GNOSIS_APPS_URL=${REACT_APP_GNOSIS_APPS_URL_STAGING}
# if: (branch = development AND NOT type = pull_request) OR (branch = master) OR tag IS present
# - env:
# - REACT_APP_NETWORK='xdai'
# - STAGING_BUCKET_NAME=${STAGING_XDAI_BUCKET_NAME}
# - REACT_APP_SENTRY_DSN=${SENTRY_DSN_XDAI}
# - SENTRY_PROJECT=${SENTRY_PROJECT_XDAI}
# - REACT_APP_GOOGLE_ANALYTICS=${REACT_APP_GOOGLE_ANALYTICS_ID_XDAI}
# if: (branch = master) OR tag IS present
# - env:
# - REACT_APP_NETWORK='volta'
# - STAGING_BUCKET_NAME=${STAGING_VOLTA_BUCKET_NAME}
# - REACT_APP_SENTRY_DSN=${SENTRY_DSN_VOLTA}
# - SENTRY_PROJECT=${SENTRY_PROJECT_VOLTA}
# - REACT_APP_GOOGLE_ANALYTICS=${REACT_APP_GOOGLE_ANALYTICS_ID_VOLTA}
# if: (branch = master) OR tag IS present
# - env:
# - REACT_APP_NETWORK='energy_web_chain'
# - STAGING_BUCKET_NAME=${STAGING_EWC_BUCKET_NAME}
# - REACT_APP_SENTRY_DSN=${SENTRY_DSN_EWC}
# - SENTRY_PROJECT=${SENTRY_PROJECT_EWC}
# - REACT_APP_GOOGLE_ANALYTICS=${REACT_APP_GOOGLE_ANALYTICS_ID_EWC}
# if: (branch = master AND NOT type = pull_request) OR tag IS present
cache:
npm: false
yarn: true
@ -50,65 +52,65 @@ before_script:
- if [ $TRAVIS_PULL_REQUEST != "false" ]; then export PUBLIC_URL="/${REACT_APP_NETWORK}/app"; fi;
before_install:
# Needed to deploy pull request and releases
- sudo apt-get update
- sudo apt-get -y install python3-pip python3-dev libusb-1.0-0-dev libudev-dev
- pip install awscli --upgrade --user
# - sudo apt-get update
# - sudo apt-get -y install python3-pip python3-dev libusb-1.0-0-dev libudev-dev
# - pip install awscli --upgrade --user
script:
- yarn prettier:check
- yarn test:coverage
- yarn build
- if [[ $TRAVIS_BRANCH == "master" && $TRAVIS_PULL_REQUEST == "false" ]] || [ -n "$TRAVIS_TAG" ]; then
echo "Upload sentry source maps";
yarn sentry-upload-sourcemaps;
else
echo "Skip source map upload";
fi;
# - yarn test:coverage
# - yarn build
# - if [[ $TRAVIS_BRANCH == "master" && $TRAVIS_PULL_REQUEST == "false" ]] || [ -n "$TRAVIS_TAG" ]; then
# echo "Upload sentry source maps";
# yarn sentry-upload-sourcemaps;
# else
# echo "Skip source map upload";
# fi;
after_success:
# Pull Request - Deploy it to a review environment
# Travis doesn't do deploy step with pull requests builds
- ./config/travis/deploy_pull_request.sh
# - ./config/travis/deploy_pull_request.sh
# Releases (tagged commits) - Deploy it to a release environment
- ./config/travis/deploy_release.sh
- yarn coveralls
# - ./config/travis/deploy_release.sh
# - yarn coveralls
deploy:
# Development environment only on rinkeby
- provider: s3
bucket: $DEV_BUCKET_NAME
access_key_id: $AWS_ACCESS_KEY_ID
secret_access_key: $AWS_SECRET_ACCESS_KEY
skip_cleanup: true
local_dir: build
upload_dir: app
region: $AWS_DEFAULT_REGION
on:
branch: development
condition: $REACT_APP_NETWORK = rinkeby
# - provider: s3
# bucket: $DEV_BUCKET_NAME
# access_key_id: $AWS_ACCESS_KEY_ID
# secret_access_key: $AWS_SECRET_ACCESS_KEY
# skip_cleanup: true
# local_dir: build
# upload_dir: app
# region: $AWS_DEFAULT_REGION
# on:
# branch: development
# condition: $REACT_APP_NETWORK = rinkeby
# Staging environment
- provider: s3
bucket: $STAGING_BUCKET_NAME
access_key_id: $AWS_ACCESS_KEY_ID
secret_access_key: $AWS_SECRET_ACCESS_KEY
skip_cleanup: true
local_dir: build
upload_dir: current/app
region: $AWS_DEFAULT_REGION
on:
branch: master
# - provider: s3
# bucket: $STAGING_BUCKET_NAME
# access_key_id: $AWS_ACCESS_KEY_ID
# secret_access_key: $AWS_SECRET_ACCESS_KEY
# skip_cleanup: true
# local_dir: build
# upload_dir: current/app
# region: $AWS_DEFAULT_REGION
# on:
# branch: master
# Prepare production deployment
- provider: s3
bucket: $STAGING_BUCKET_NAME
secret_access_key: $AWS_SECRET_ACCESS_KEY
access_key_id: $AWS_ACCESS_KEY_ID
skip_cleanup: true
local_dir: build
upload_dir: releases/$TRAVIS_TAG
region: $AWS_DEFAULT_REGION
on:
tags: true
- provider: script
script: ./config/travis/prepare_production_deployment.sh
on:
tags: true
# - provider: s3
# bucket: $STAGING_BUCKET_NAME
# secret_access_key: $AWS_SECRET_ACCESS_KEY
# access_key_id: $AWS_ACCESS_KEY_ID
# skip_cleanup: true
# local_dir: build
# upload_dir: releases/$TRAVIS_TAG
# region: $AWS_DEFAULT_REGION
# on:
# tags: true
# - provider: script
# script: ./config/travis/prepare_production_deployment.sh
# on:
# tags: true

View File

@ -92,12 +92,10 @@ export enum WALLETS {
TREZOR = 'trezor',
LEDGER = 'ledger',
TRUST = 'trust',
DAPPER = 'dapper',
FORTMATIC = 'fortmatic',
PORTIS = 'portis',
AUTHEREUM = 'authereum',
TORUS = 'torus',
UNILOGIN = 'unilogin',
COINBASE = 'coinbase',
WALLET_LINK = 'walletLink',
OPERA = 'opera',

View File

@ -1,6 +1,6 @@
{
"name": "safe-react",
"version": "3.0.1",
"version": "3.1.0",
"description": "Allowing crypto users manage funds in a safer way",
"website": "https://github.com/gnosis/safe-react#readme",
"bugs": {
@ -161,7 +161,7 @@
"@gnosis.pm/safe-apps-sdk": "1.0.3",
"@gnosis.pm/safe-apps-sdk-v1": "npm:@gnosis.pm/safe-apps-sdk@0.4.2",
"@gnosis.pm/safe-contracts": "1.1.1-dev.2",
"@gnosis.pm/safe-react-components": "https://github.com/gnosis/safe-react-components.git#8dea3a6",
"@gnosis.pm/safe-react-components": "https://github.com/gnosis/safe-react-components.git#fb1a523",
"@gnosis.pm/util-contracts": "2.0.6",
"@ledgerhq/hw-transport-node-hid-singleton": "5.41.0",
"@material-ui/core": "^4.11.0",
@ -175,7 +175,7 @@
"async-sema": "^3.1.0",
"axios": "0.21.1",
"bignumber.js": "9.0.1",
"bnc-onboard": "^1.16.1",
"bnc-onboard": "1.19.2",
"classnames": "^2.2.6",
"concurrently": "^5.3.0",
"connected-react-router": "6.8.0",
@ -215,7 +215,7 @@
"react-final-form-listeners": "^1.0.2",
"react-ga": "3.3.0",
"react-hot-loader": "4.13.0",
"react-infinite-scroll-component": "^5.1.0",
"react-intersection-observer": "^8.31.0",
"react-qr-reader": "^2.2.1",
"react-redux": "7.2.2",
"react-router-dom": "5.2.0",

View File

@ -0,0 +1,22 @@
#!/bin/bash
function deploy_pull_request {
# Pull request number with "pr" prefix
PULL_REQUEST_NUMBER="pr$PR_NUMBER"
# Only alphanumeric characters. Example safe-react -> safereact
REVIEW_FEATURE_FOLDER="$REPO_NAME_ALPHANUMERIC/$PULL_REQUEST_NUMBER"
# App build path
APP_PATH="./build"
# Deploy pull request
aws s3 sync $APP_PATH s3://${REVIEW_BUCKET_NAME}/${REVIEW_FEATURE_FOLDER}/${REACT_APP_NETWORK}/app --delete
}
# Only:
# - Pull requests
# - Security env variables are available. PR from forks don't have them.
if [ -n "$AWS_ACCESS_KEY_ID" ]; then
deploy_pull_request
fi

View File

@ -3,19 +3,12 @@
# Only:
# - Tagged commits
# - Security env variables are available.
if [ -n "$TRAVIS_TAG" ] && [ -n "$AWS_ACCESS_KEY_ID" ]
if [ -n "$VERSION_TAG" ] && [ -n "$AWS_ACCESS_KEY_ID" ]
then
REVIEW_ENVIRONMENT_DOMAIN='review.gnosisdev.com'
# Feature name without all path. Example gnosis/pm-trading-ui -> pm-trading-ui
REPO_NAME=$(basename $TRAVIS_REPO_SLUG)
# Only alphanumeric characters. Example pm-trading-ui -> pmtradingui
REPO_NAME_ALPHANUMERIC=$(echo $REPO_NAME | sed 's/[^a-zA-Z0-9]//g')
# Only alphanumeric characters. Example v1.0.0 -> v100
TRAVIS_TAG_ALPHANUMERIC=$(echo $TRAVIS_TAG | sed 's/[^a-zA-Z0-9]//g')
VERSION_TAG_ALPHANUMERIC=$(echo $VERSION_TAG | sed 's/[^a-zA-Z0-9]//g')
REVIEW_RELEASE_FOLDER="$REPO_NAME_ALPHANUMERIC/$TRAVIS_TAG_ALPHANUMERIC"
REVIEW_RELEASE_FOLDER="$REPO_NAME_ALPHANUMERIC/$VERSION_TAG_ALPHANUMERIC"
# Deploy safe-team release project
aws s3 sync build s3://${REVIEW_BUCKET_NAME}/${REVIEW_RELEASE_FOLDER}/app --delete --exclude "*.html" --exclude "/page-data" --cache-control max-age=31536000,public

View File

@ -5,12 +5,12 @@ set -ev
# Only:
# - Tagged commits
# - Security env variables are available.
if [ -n "$TRAVIS_TAG" ] && [ -n "$PROD_DEPLOYMENT_HOOK_TOKEN" ] && [ -n "$PROD_DEPLOYMENT_HOOK_URL" ]
if [ -n "$VERSION_TAG" ] && [ -n "$PROD_DEPLOYMENT_HOOK_TOKEN" ] && [ -n "$PROD_DEPLOYMENT_HOOK_URL" ]
then
curl --silent --output /dev/null --write-out "%{http_code}" -X POST \
-F token="$PROD_DEPLOYMENT_HOOK_TOKEN" \
-F ref=master \
-F "variables[TRIGGER_RELEASE_COMMIT_TAG]=$TRAVIS_TAG" \
-F "variables[TRIGGER_RELEASE_COMMIT_TAG]=$VERSION_TAG" \
$PROD_DEPLOYMENT_HOOK_URL
else
echo "[ERROR] Production deployment could not be prepared"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.2 KiB

View File

@ -1,77 +0,0 @@
<svg fill="none" xmlns="http://www.w3.org/2000/svg" width="40" viewBox="0 0 38 33">
<path fill-rule="evenodd" clip-rule="evenodd" d="M60.5108 17.1193V7.87679H62.682V17.1193C62.682 18.6187 62.1747 19.851 61.1602 20.8163C60.1659 21.7611 58.9484 22.2335 57.5077 22.2335C56.067 22.2335 54.8292 21.7611 53.8147 20.8163C52.8204 19.851 52.3131 18.6187 52.3131 17.1193V7.87679H54.5046V17.1193C54.5046 18.0025 54.7887 18.7419 55.3771 19.2965C55.9656 19.851 56.6758 20.1386 57.5077 20.1386C58.3397 20.1386 59.0296 19.851 59.618 19.2965C60.2065 18.7419 60.5108 18.0025 60.5108 17.1193ZM67.6043 16.872V22.0273H65.5143V12.4356H67.6043V13.8733C68.1116 12.8258 69.3697 12.2302 70.506 12.2302C72.7989 12.2302 74.1178 13.7295 74.1178 16.4407V22.0273H72.0278V16.6871C72.0278 15.1878 71.2568 14.3046 70.0393 14.3046C68.6595 14.3046 67.6043 15.1262 67.6043 16.872ZM79.0079 22.0274H76.9381V12.4357H79.0079V22.0274ZM77.9731 7.21899C78.7645 7.21899 79.3529 7.79376 79.3529 8.55327C79.3529 9.31279 78.7645 9.88755 77.9731 9.88755C77.2223 9.88755 76.573 9.29226 76.573 8.55327C76.573 7.81429 77.2223 7.21899 77.9731 7.21899ZM90.412 22.0281H82.6607V7.87679H84.8116V20.0359H90.412V22.0281ZM122.079 16.872V22.0273H119.989V12.4356H122.079V13.8733C122.586 12.8258 123.844 12.2302 124.981 12.2302C127.274 12.2302 128.592 13.7295 128.592 16.4407V22.0273H126.502V16.6871C126.502 15.1878 125.731 14.3046 124.514 14.3046C123.134 14.3046 122.079 15.1262 122.079 16.872Z" fill="#12083A"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M96.43 20.1786C97.2417 20.1786 97.9316 19.9116 98.4794 19.3571C99.0273 18.8025 99.3114 18.0837 99.3114 17.2005C99.3114 15.4752 98.0533 14.3045 96.43 14.3045C95.6386 14.3045 94.9487 14.5715 94.4009 15.1055C93.853 15.6395 93.5689 16.3378 93.5689 17.2005C93.5689 18.0837 93.853 18.8025 94.4009 19.3571C94.9487 19.9116 95.6386 20.1786 96.43 20.1786ZM96.4298 12.23C97.8299 12.23 99.0068 12.6818 99.9605 13.6061C100.914 14.5303 101.401 15.7216 101.401 17.2004C101.401 18.6792 100.914 19.891 99.9402 20.8358C98.9865 21.7601 97.8096 22.2325 96.4298 22.2325C95.05 22.2325 93.8731 21.7601 92.9194 20.8358C91.9657 19.891 91.499 18.6792 91.499 17.2004C91.499 15.7216 91.9657 14.5303 92.9194 13.6061C93.8731 12.6818 95.05 12.23 96.4298 12.23Z" fill="#12083A"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M117.412 8.55327C117.412 7.79376 116.823 7.21899 116.032 7.21899C115.281 7.21899 114.632 7.81429 114.632 8.55327C114.632 9.29226 115.281 9.88755 116.032 9.88755C116.823 9.88755 117.412 9.31279 117.412 8.55327ZM114.997 22.0275H117.067V12.4358H114.997V22.0275Z" fill="#12083A"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M111.471 14.9307C111.531 14.9144 111.592 14.8962 111.652 14.8761C112.41 14.6232 112.868 14.1 113.459 13.4244C113.595 13.2693 113.738 13.1061 113.893 12.9363L112.449 11.6182C112.201 11.8898 112.011 12.1135 111.855 12.2971C111.501 12.7147 111.323 12.9251 111.034 13.0214C110.759 13.1131 110.432 13.129 109.906 12.9673C109.159 12.4521 108.286 12.1991 107.287 12.1991C106.045 12.1991 104.985 12.6018 104.127 13.4255C103.268 14.2493 102.848 15.311 102.848 16.6289C102.848 17.5631 103.059 18.3776 103.488 19.0727L102.94 19.999L102.936 20.0072C102.271 21.1831 102.279 22.5147 103.046 23.496C103.807 24.4696 105.117 24.8493 106.55 24.5763C108.04 24.2926 109.151 24.5195 109.754 24.9013C110.046 25.0858 110.197 25.2897 110.265 25.4766C110.33 25.6575 110.347 25.9067 110.221 26.2482C110.015 26.6488 109.425 27.13 108.176 27.2317C106.93 27.3333 105.218 27.0194 103.238 26.3166L102.459 27.9536C104.624 28.8929 106.667 29.3162 108.335 29.1803C110 29.0446 111.483 28.3136 112.054 26.7708C112.319 26.0566 112.346 25.4847 112.103 24.8127C111.863 24.1467 111.389 23.6224 110.8 23.2497C109.644 22.5172 107.979 22.314 106.184 22.6559C105.272 22.8297 104.784 22.5448 104.586 22.2924C104.396 22.0486 104.287 21.5955 104.634 20.9766L104.925 20.4839C105.616 20.9016 106.405 21.1137 107.287 21.1137C108.529 21.1137 109.589 20.6927 110.448 19.869C111.324 19.0269 111.763 17.9469 111.763 16.6289C111.763 16.0063 111.665 15.4409 111.471 14.9307ZM109.027 18.5371C108.565 19.0096 107.982 19.2371 107.297 19.2371C106.629 19.2371 106.047 19.0096 105.584 18.5371C105.121 18.0646 104.882 17.4521 104.882 16.6997C104.882 15.9647 105.121 15.3698 105.584 14.9148C106.047 14.4598 106.629 14.2323 107.297 14.2323C108.668 14.2323 109.73 15.2298 109.73 16.6997C109.73 17.4521 109.49 18.0646 109.027 18.5371Z" fill="#12083A"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H4.41438C9.35059 0 13.3522 3.95072 13.3522 8.82418V15.8177C13.3522 18.1572 15.2731 20.0537 17.6427 20.0537C20.0123 20.0537 21.9332 18.1572 21.9332 15.8177V8.82418C21.9332 3.95072 25.9348 0 30.871 0H35.36V15.5447C35.36 25.185 27.4444 33 17.68 33C7.91561 33 0 25.185 0 15.5447V0Z" fill="url(#paint0_linear)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H4.41438C9.35059 0 13.3522 3.95072 13.3522 8.82418V15.8177C13.3522 18.1572 15.2731 20.0537 17.6427 20.0537C20.0123 20.0537 21.9332 18.1572 21.9332 15.8177V8.82418C21.9332 3.95072 25.9348 0 30.871 0H35.36V15.5447C35.36 25.185 27.4444 33 17.68 33C7.91561 33 0 25.185 0 15.5447V0Z" fill="url(#paint1_linear)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H4.41438C9.35059 0 13.3522 3.95072 13.3522 8.82418V15.8177C13.3522 18.1572 15.2731 20.0537 17.6427 20.0537C20.0123 20.0537 21.9332 18.1572 21.9332 15.8177V8.82418C21.9332 3.95072 25.9348 0 30.871 0H35.36V15.5447C35.36 25.185 27.4444 33 17.68 33C7.91561 33 0 25.185 0 15.5447V0Z" fill="url(#paint2_linear)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H4.41438C9.35059 0 13.3522 3.95072 13.3522 8.82418V15.8177C13.3522 18.1572 15.2731 20.0537 17.6427 20.0537C20.0123 20.0537 21.9332 18.1572 21.9332 15.8177V8.82418C21.9332 3.95072 25.9348 0 30.871 0H35.36V15.5447C35.36 25.185 27.4444 33 17.68 33C7.91561 33 0 25.185 0 15.5447V0Z" fill="url(#paint3_linear)"/>
<path opacity="0.973865" fill-rule="evenodd" clip-rule="evenodd" d="M18.4081 20.0421L20.5261 21.0203L24.6422 20.6489L26.5122 20.0421L28.6999 19.2688L31.1574 17.2003L33.1717 16.1934L34.1053 13.4891L34.4379 11.2345L34.941 9.61423V6.93811L35.2474 4.47731V2.51147C34.6562 6.52673 34.3095 7.49061 33.746 8.63864C32.9008 10.3607 32.9008 10.396 31.7599 11.9479C30.6191 13.4998 29.9889 14.2432 28.2374 15.646C26.4859 17.0487 25.7999 17.4505 23.8462 18.2507C22.5438 18.7842 21.1991 19.1924 19.8123 19.4754L18.4081 20.0421Z" fill="url(#paint4_linear)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H4.41438C9.35059 0 13.3522 3.95072 13.3522 8.82418V15.8177C13.3522 18.1572 15.2731 20.0537 17.6427 20.0537C20.0123 20.0537 21.9332 18.1572 21.9332 15.8177V8.82418C21.9332 3.95072 25.9348 0 30.871 0H35.36V15.5447C35.36 25.185 27.4444 33 17.68 33C7.91561 33 0 25.185 0 15.5447V0Z" fill="url(#paint5_linear)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H4.41438C9.35059 0 13.3522 3.95072 13.3522 8.82418V15.7809C13.3522 18.1407 15.2898 20.0537 17.68 20.0537C20.4898 20.0537 23.2606 19.4044 25.7707 18.1579L25.9658 18.061C28.5968 16.7544 30.8439 14.8029 32.4921 12.3933L32.5075 12.3708C34.1604 9.95431 35.0942 7.12876 35.2028 4.2148L35.36 0V15.5447C35.36 25.185 27.4444 33 17.68 33C7.91561 33 0 25.185 0 15.5447V0Z" fill="url(#paint6_linear)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H4.41438C9.35059 0 13.3522 3.95072 13.3522 8.82418V15.7809C13.3522 18.1407 15.2898 20.0537 17.68 20.0537C20.4898 20.0537 23.2606 19.4044 25.7707 18.1579L25.9658 18.061C28.5968 16.7544 30.8439 14.8029 32.4921 12.3933L32.5075 12.3708C34.1604 9.95431 35.0942 7.12876 35.2028 4.2148L35.36 0V15.5447C35.36 25.185 27.4444 33 17.68 33C7.91561 33 0 25.185 0 15.5447V0Z" fill="url(#paint7_linear)"/>
<mask id="mask0" mask-type="alpha" maskUnits="userSpaceOnUse" x="0" y="0" width="36" height="33">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H4.41438C9.35059 0 13.3522 3.95072 13.3522 8.82418V15.7809C13.3522 18.1407 15.2898 20.0537 17.68 20.0537C20.4898 20.0537 23.2606 19.4044 25.7707 18.1579L25.9658 18.061C28.5968 16.7544 30.8439 14.8029 32.4921 12.3933L32.5075 12.3708C34.1604 9.95431 35.0942 7.12876 35.2028 4.2148L35.36 0V15.5447C35.36 25.185 27.4444 33 17.68 33C7.91561 33 0 25.185 0 15.5447V0Z" fill="url(#paint8_linear)"/>
</mask>
<g mask="url(#mask0)">
<path opacity="0.5" fill-rule="evenodd" clip-rule="evenodd" d="M13.3353 13.4179L12.9774 15.9216L12.487 20.1442L11.1426 21.6316L10.8473 23.9072L11.5984 27.008L11.5926 29.2371L13.6093 31.286L15.4931 32.6023L16.7269 33.7804L19.1421 34.9953L21.2238 36.382L22.9979 37.2745L21.5602 36.2802C20.7138 35.6948 19.9293 35.0267 19.2186 34.286L18.7646 33.8128C18.3584 33.3893 17.8758 33.0443 17.3416 32.7953C16.823 32.5537 16.4196 32.1228 16.2162 31.5934L16.0131 31.0649C15.6693 30.1699 15.4931 29.2206 15.4931 28.2633L15.4931 19.4755L14.731 18.8772L14.0364 18.0917L13.8629 17.7928L13.8214 17.7083C13.7616 17.5864 13.7059 17.4626 13.6543 17.3372C13.6027 17.2117 13.5579 17.0837 13.5199 16.9537L13.5143 16.9346C13.4726 16.7919 13.4401 16.6467 13.4168 16.5L13.3969 16.374L13.3353 15.8616L13.3353 13.4179Z" fill="url(#paint9_linear)"/>
</g>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H4.41438C9.35059 0 13.3522 3.95072 13.3522 8.82418V15.7809C13.3522 18.1407 15.2898 20.0537 17.68 20.0537C20.4898 20.0537 23.2606 19.4044 25.7707 18.1579L25.9658 18.061C28.5968 16.7544 30.8439 14.8029 32.4921 12.3933L32.5075 12.3708C34.1604 9.95431 35.0942 7.12876 35.2028 4.2148L35.36 0V15.5447C35.36 25.185 27.4444 33 17.68 33C7.91561 33 0 25.185 0 15.5447V0Z" fill="url(#paint10_linear)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H4.41438C9.3506 0 13.3522 3.95072 13.3522 8.82418V10.9597V28.7272C13.3522 31.087 15.2898 33 17.68 33C7.91562 33 0 25.185 0 15.5447V0Z" fill="url(#paint11_linear)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 0H4.41438C9.3506 0 13.3522 3.95072 13.3522 8.82418V10.9597V28.7272C13.3522 31.087 15.2898 33 17.68 33C7.91562 33 0 25.185 0 15.5447V0Z" fill="url(#paint12_linear)"/>
<defs>
<linearGradient id="paint0_linear" x1="17.68" y1="0" x2="17.68" y2="33" gradientUnits="userSpaceOnUse">
<stop stop-color="#DEEDFF"/>
<stop offset="1" stop-color="#B2D4FD"/>
</linearGradient>
<linearGradient id="paint1_linear" x1="32.0043" y1="1.75656" x2="30.9757" y2="20.7371" gradientUnits="userSpaceOnUse">
<stop stop-color="#00D6D9"/>
<stop offset="1" stop-color="#455CDA"/>
</linearGradient>
<linearGradient id="paint2_linear" x1="22.8708" y1="1.75656" x2="29.9573" y2="11.9966" gradientUnits="userSpaceOnUse">
<stop stop-color="#00D6D9"/>
<stop offset="1" stop-color="#04CFD9" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint3_linear" x1="22.931" y1="16.5" x2="25.2362" y2="19.5271" gradientUnits="userSpaceOnUse">
<stop stop-color="#3F66DA" stop-opacity="0"/>
<stop offset="1" stop-color="#2E2EAE"/>
</linearGradient>
<linearGradient id="paint4_linear" x1="24.8937" y1="13.3057" x2="27.177" y2="19.1536" gradientUnits="userSpaceOnUse">
<stop stop-color="#2D41B3" stop-opacity="0"/>
<stop offset="1" stop-color="#141E62"/>
</linearGradient>
<linearGradient id="paint5_linear" x1="15.3553" y1="7.8919" x2="28.5471" y2="23.0918" gradientUnits="userSpaceOnUse">
<stop stop-color="#3F66DA" stop-opacity="0"/>
<stop offset="1" stop-color="#2436C1"/>
</linearGradient>
<linearGradient id="paint6_linear" x1="43.3468" y1="40.5914" x2="31.3119" y2="9.34286" gradientUnits="userSpaceOnUse">
<stop stop-color="#706AE1"/>
<stop offset="1" stop-color="#4352ED"/>
</linearGradient>
<linearGradient id="paint7_linear" x1="67.4184" y1="1.75656" x2="37.8858" y2="38.7384" gradientUnits="userSpaceOnUse">
<stop stop-color="#00D6D9"/>
<stop offset="1" stop-color="#0FC4DD" stop-opacity="0"/>
</linearGradient>
<linearGradient id="paint8_linear" x1="24.7435" y1="23.2722" x2="14.1909" y2="30.3859" gradientUnits="userSpaceOnUse">
<stop stop-color="#3F4DDA" stop-opacity="0"/>
<stop offset="1" stop-color="#2736A4"/>
</linearGradient>
<linearGradient id="paint9_linear" x1="19.1948" y1="23.354" x2="13.0623" y2="23.3828" gradientUnits="userSpaceOnUse">
<stop stop-color="#2D41B3" stop-opacity="0"/>
<stop offset="1" stop-color="#141E62"/>
</linearGradient>
<linearGradient id="paint10_linear" x1="24.7435" y1="23.2722" x2="14.1909" y2="30.3859" gradientUnits="userSpaceOnUse">
<stop stop-color="#3F4DDA" stop-opacity="0"/>
<stop offset="1" stop-color="#2736A4"/>
</linearGradient>
<linearGradient id="paint11_linear" x1="6.79858" y1="1.44448e-07" x2="17.0045" y2="23.042" gradientUnits="userSpaceOnUse">
<stop stop-color="#00D6D9"/>
<stop offset="1" stop-color="#5272EE"/>
</linearGradient>
<linearGradient id="paint12_linear" x1="4.91662" y1="8.00639" x2="12.8211" y2="20.2053" gradientUnits="userSpaceOnUse">
<stop stop-color="#00D6D9"/>
<stop offset="1" stop-color="#0FC4DD" stop-opacity="0"/>
</linearGradient>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 13 KiB

View File

@ -4,12 +4,10 @@ import walletConnectIcon from './icon-wallet-connect.svg'
import trezorIcon from './icon-trezor.svg'
import ledgerIcon from './icon-ledger.svg'
import latticeIcon from './icon-lattice.svg'
import dapperIcon from './icon-dapper.png'
import fortmaticIcon from './icon-fortmatic.svg'
import portisIcon from './icon-portis.svg'
import authereumIcon from './icon-authereum.png'
import torusIcon from './icon-torus.svg'
import uniloginIcon from './icon-unilogin.svg'
import coinbaseIcon from './icon-coinbase.svg'
import operaIcon from './icon-opera.png'
@ -47,10 +45,6 @@ const WALLET_ICONS: WalletObjectsProps<IconValue> = {
src: latticeIcon,
height: 41,
},
[WALLET_PROVIDER.DAPPER]: {
src: dapperIcon,
height: 25,
},
[WALLET_PROVIDER.FORTMATIC]: {
src: fortmaticIcon,
height: 25,
@ -67,10 +61,6 @@ const WALLET_ICONS: WalletObjectsProps<IconValue> = {
src: torusIcon,
height: 30,
},
[WALLET_PROVIDER.UNILOGIN]: {
src: uniloginIcon,
height: 25,
},
[WALLET_PROVIDER.OPERA]: {
src: operaIcon,
height: 25,

View File

@ -44,7 +44,9 @@ const IconContainer = styled.div`
justify-content: space-evenly;
`
const StyledButton = styled(Button)`
padding: 0 18px;
&&.MuiButton-root {
padding: 0 16px;
}
*:first-child {
margin: 0 4px 0 0;
}

View File

@ -1,39 +1,53 @@
import { Loader } from '@gnosis.pm/safe-react-components'
import React, { ReactElement } from 'react'
import { default as ReactInfiniteScroll, Props as ReactInfiniteScrollProps } from 'react-infinite-scroll-component'
import styled from 'styled-components'
import React, { createContext, forwardRef, MutableRefObject, ReactElement, ReactNode, useEffect, useState } from 'react'
import { InViewHookResponse, useInView } from 'react-intersection-observer'
import { Overwrite } from 'src/types/helpers'
export const INFINITE_SCROLL_CONTAINER = 'infinite-scroll-container'
export const Centered = styled.div<{ padding?: number }>`
width: 100%;
height: 100%;
display: flex;
padding: ${({ padding }) => `${padding}px`};
justify-content: center;
align-items: center;
`
export const InfiniteScrollContext = createContext<{
ref: MutableRefObject<HTMLDivElement | null> | ((instance: HTMLDivElement | null) => void) | null
lastItemId?: string
setLastItemId: (itemId?: string) => void
}>({ setLastItemId: () => {}, ref: null })
export const SCROLLABLE_TARGET_ID = 'scrollableDiv'
export const InfiniteScrollProvider = forwardRef<HTMLDivElement, { children: ReactNode }>(
({ children }, ref): ReactElement => {
const [lastItemId, _setLastItemId] = useState<string>()
type InfiniteScrollProps = Overwrite<ReactInfiniteScrollProps, { loader?: ReactInfiniteScrollProps['loader'] }>
const setLastItemId = (itemId?: string) => {
setTimeout(() => _setLastItemId(itemId), 0)
}
export const InfiniteScroll = ({ dataLength, next, hasMore, ...props }: InfiniteScrollProps): ReactElement => {
return (
<ReactInfiniteScroll
style={{ overflow: 'hidden' }}
dataLength={dataLength}
next={next}
hasMore={hasMore}
loader={
<Centered>
<Loader size="md" />
</Centered>
}
scrollThreshold="120px"
scrollableTarget={SCROLLABLE_TARGET_ID}
>
{props.children}
</ReactInfiniteScroll>
)
return (
<InfiniteScrollContext.Provider value={{ ref, lastItemId, setLastItemId }}>
{children}
</InfiniteScrollContext.Provider>
)
},
)
InfiniteScrollProvider.displayName = 'InfiniteScrollProvider'
type InfiniteScrollProps = {
children: ReactNode
hasMore: boolean
next: () => Promise<void>
config?: InViewHookResponse
}
export const InfiniteScroll = ({ children, hasMore, next, config }: InfiniteScrollProps): ReactElement => {
const { ref, inView } = useInView({
threshold: 0,
root: document.querySelector(`#${INFINITE_SCROLL_CONTAINER}`),
rootMargin: '0px 0px 200px 0px',
triggerOnce: true,
...config,
})
useEffect(() => {
if (inView && hasMore) {
next()
}
}, [inView, hasMore, next])
return <InfiniteScrollProvider ref={ref}>{children}</InfiniteScrollProvider>
}

View File

@ -1,10 +1,10 @@
import { withStyles } from '@material-ui/core/styles'
import React from 'react'
import { createStyles, makeStyles } from '@material-ui/core/styles'
import React, { ReactElement } from 'react'
import Field from 'src/components/forms/Field'
import TextField from 'src/components/forms/TextField'
const styles = () => ({
const styles = createStyles({
textarea: {
'& > div': {
height: '140px',
@ -21,8 +21,9 @@ const styles = () => ({
},
})
const TextareaField = ({ classes, ...props }) => (
<Field {...props} className={classes.textarea} component={TextField} multiline rows="5" />
)
const useStyles = makeStyles(styles)
export default withStyles(styles as any)(TextareaField)
export const TextAreaField = ({ ...props }): ReactElement => {
const classes = useStyles()
return <Field {...props} className={classes.textarea} component={TextField} multiline rows="5" />
}

View File

@ -49,14 +49,12 @@ const mainnet: NetworkConfig = {
WALLETS.TREZOR,
WALLETS.LEDGER,
WALLETS.COINBASE,
WALLETS.DAPPER,
WALLETS.FORTMATIC,
WALLETS.OPERA,
WALLETS.OPERA_TOUCH,
WALLETS.PORTIS,
WALLETS.TORUS,
WALLETS.TRUST,
WALLETS.UNILOGIN,
WALLETS.WALLET_CONNECT,
WALLETS.WALLET_LINK,
WALLETS.AUTHEREUM,

View File

@ -6,12 +6,10 @@ export enum WALLETS {
TREZOR = 'trezor',
LEDGER = 'ledger',
TRUST = 'trust',
DAPPER = 'dapper',
FORTMATIC = 'fortmatic',
PORTIS = 'portis',
AUTHEREUM = 'authereum',
TORUS = 'torus',
UNILOGIN = 'unilogin',
COINBASE = 'coinbase',
WALLET_LINK = 'walletLink',
OPERA = 'opera',

View File

@ -46,14 +46,12 @@ const mainnet: NetworkConfig = {
WALLETS.TREZOR,
WALLETS.LEDGER,
WALLETS.COINBASE,
WALLETS.DAPPER,
WALLETS.FORTMATIC,
WALLETS.OPERA,
WALLETS.OPERA_TOUCH,
WALLETS.PORTIS,
WALLETS.TORUS,
WALLETS.TRUST,
WALLETS.UNILOGIN,
WALLETS.WALLET_LINK,
WALLETS.AUTHEREUM,
WALLETS.LATTICE,

View File

@ -40,13 +40,11 @@ const xDai: NetworkConfig = {
WALLETS.TREZOR,
WALLETS.LEDGER,
WALLETS.COINBASE,
WALLETS.DAPPER,
WALLETS.FORTMATIC,
WALLETS.OPERA,
WALLETS.OPERA_TOUCH,
WALLETS.TORUS,
WALLETS.TRUST,
WALLETS.UNILOGIN,
WALLETS.WALLET_CONNECT,
WALLETS.WALLET_LINK,
WALLETS.AUTHEREUM,

View File

@ -1,10 +1,10 @@
import { RateLimit } from 'async-sema'
import { Collectibles, NFTAsset, NFTAssets, NFTTokens } from 'src/logic/collectibles/sources/collectibles.d'
import { sameAddress } from 'src/logic/wallets/ethAddresses'
import NFTIcon from 'src/routes/safe/components/Balances/assets/nft_icon.png'
import { fetchErc20AndErc721AssetsList, fetchSafeCollectibles } from 'src/logic/tokens/api'
import { TokenResult } from 'src/logic/tokens/api/fetchErc20AndErc721AssetsList'
import { CollectibleResult } from 'src/logic/tokens/api/fetchSafeCollectibles'
import { sameString } from 'src/utils/strings'
type FetchResult = {
erc721Assets: TokenResult[]
@ -12,67 +12,67 @@ type FetchResult = {
}
class Gnosis {
_rateLimit = async (): Promise<void> => {}
_fetch = async (safeAddress: string): Promise<FetchResult> => {
const collectibles: FetchResult = {
erc721Assets: [],
erc721Tokens: [],
}
const [assets, tokens] = await Promise.allSettled([
fetchErc20AndErc721AssetsList(),
fetchSafeCollectibles(safeAddress),
])
try {
const {
data: { results: assets = [] },
} = await fetchErc20AndErc721AssetsList()
collectibles.erc721Assets = assets.filter((token) => token.type.toLowerCase() === 'erc721')
} catch (e) {
console.error('no erc721 assets could be fetched', e)
switch (assets.status) {
case 'fulfilled':
const {
data: { results = [] },
} = assets.value
collectibles.erc721Assets = results.filter((token) => sameString(token.type, 'erc721'))
break
case 'rejected':
console.error('no erc721 assets could be fetched', assets.reason)
break
}
try {
const { data: tokens = [] } = await fetchSafeCollectibles(safeAddress)
collectibles.erc721Tokens = tokens
} catch (e) {
console.error('no erc721 tokens for the current safe', e)
switch (tokens.status) {
case 'fulfilled':
collectibles.erc721Tokens = tokens.value.data || []
break
case 'rejected':
console.error('no erc721 tokens for the current safe', tokens.reason)
break
}
return collectibles
}
/**
* OpenSea class constructor
* @param {object} options
* @param {number} options.rps - requests per second
*/
constructor(options: { rps: number }) {
// eslint-disable-next-line no-underscore-dangle
this._rateLimit = RateLimit(options.rps, { timeUnit: 60 * 1000, uniformDistribution: true })
static extractNFTAsset = (asset: TokenResult, nftTokens: NFTTokens): NFTAsset => {
const mainAssetAddress = asset.address
const numberOfTokens = nftTokens.filter(({ assetAddress }) => sameAddress(assetAddress, mainAssetAddress)).length
return {
address: mainAssetAddress,
description: asset.name,
image: asset.logoUri || NFTIcon,
name: asset.name,
numberOfTokens,
slug: `${mainAssetAddress}_${asset.name}`,
symbol: asset.symbol,
}
}
static extractAssets(assets: TokenResult[], nftTokens: NFTTokens): NFTAssets {
const extractNFTAsset = (asset: TokenResult): NFTAsset => {
const numberOfTokens = nftTokens.filter(({ assetAddress }) => assetAddress === asset.address).length
const extractedAssets = {}
return {
address: asset.address,
description: asset.name,
image: asset.logoUri || NFTIcon,
name: asset.name,
numberOfTokens,
slug: `${asset.address}_${asset.name}`,
symbol: asset.symbol,
}
}
return assets.reduce((acc, asset) => {
assets.forEach((asset) => {
const address = asset.address
if (acc[address] === undefined) {
acc[address] = extractNFTAsset(asset)
if (extractedAssets[address] === undefined) {
extractedAssets[address] = Gnosis.extractNFTAsset(asset, nftTokens)
}
})
return acc
}, {})
return extractedAssets
}
static extractTokens(tokens: CollectibleResult[]): NFTTokens {
@ -94,12 +94,11 @@ class Gnosis {
*/
async fetchCollectibles(safeAddress: string): Promise<Collectibles> {
const { erc721Assets, erc721Tokens } = await this._fetch(safeAddress)
const nftTokens = Gnosis.extractTokens(erc721Tokens)
return {
nftTokens,
nftAssets: Gnosis.extractAssets(erc721Assets, nftTokens),
}
const nftTokens = Gnosis.extractTokens(erc721Tokens)
const nftAssets = Gnosis.extractAssets(erc721Assets, nftTokens)
return { nftTokens, nftAssets }
}
}

View File

@ -5,7 +5,7 @@ import { COLLECTIBLES_SOURCE } from 'src/utils/constants'
const SOURCES = {
opensea: new OpenSea({ rps: 4 }),
gnosis: new Gnosis({ rps: 4 }),
gnosis: new Gnosis(),
mockedopensea: new MockedOpenSea({ rps: 4 }),
}

View File

@ -1,4 +1,3 @@
import { batch } from 'react-redux'
import { Dispatch } from 'redux'
import { getConfiguredSource } from 'src/logic/collectibles/sources'
@ -9,10 +8,8 @@ export const fetchCollectibles = (safeAddress: string) => async (dispatch: Dispa
const source = getConfiguredSource()
const collectibles = await source.fetchCollectibles(safeAddress)
batch(() => {
dispatch(addNftAssets(collectibles.nftAssets))
dispatch(addNftTokens(collectibles.nftTokens))
})
dispatch(addNftAssets(collectibles.nftAssets))
dispatch(addNftTokens(collectibles.nftTokens))
} catch (error) {
console.log('Error fetching collectibles:', error)
}

View File

@ -4,7 +4,8 @@ import {
estimateGasForTransactionApproval,
estimateGasForTransactionCreation,
estimateGasForTransactionExecution,
MINIMUM_TRANSACTION_GAS,
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'
@ -38,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
}
@ -212,6 +217,8 @@ export const useEstimateTransactionGas = ({
preApprovingOwner,
)
const fixedGasCosts = getFixedGasCosts(Number(threshold))
try {
const isOffChainSignature = checkIfOffChainSignatureIsPossible(isExecution, smartContractWallet, safeVersion)
@ -229,12 +236,13 @@ export const useEstimateTransactionGas = ({
safeTxGas,
approvalAndExecution,
})
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 + MINIMUM_TRANSACTION_GAS).toString()
const gasLimit = ((gasEstimation + fixedGasCosts) * 2).toString()
let txEstimationExecutionStatus = EstimationStatus.SUCCESS
@ -257,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
const gasEstimation = fixedGasCosts + SAFE_TX_GAS_DATA_COST
const gasCost = fromTokenUnit(gasEstimation, nativeCoin.decimals)
const gasCostFormatted = formatAmount(gasCost)
setGasEstimation({

View File

@ -28,7 +28,7 @@ export const useSafeScheduledUpdates = (safeLoaded: boolean, safeAddress?: strin
})
if (mounted) {
timer.current = setTimeout(() => {
timer.current = window.setTimeout(() => {
fetchSafeData(address)
}, TIMEOUT * 3)
}

View File

@ -80,13 +80,15 @@ export const createTransaction = (
const safeInstance = await getGnosisSafeInstanceAt(safeAddress)
const lastTx = await getLastTx(safeAddress)
const nextNonce = await getNewTxNonce(lastTx, safeInstance)
const nonce = txNonce ? txNonce.toString() : nextNonce
const nonce = txNonce !== undefined ? txNonce.toString() : nextNonce
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
}

View File

@ -122,13 +122,16 @@ export const checkAndUpdateSafe = (safeAdd: string) => async (dispatch: Dispatch
name: localSafe?.name,
modules,
spendingLimits,
nonce: Number(remoteNonce),
threshold: Number(remoteThreshold),
nonce: remoteNonce ? Number(remoteNonce) : undefined,
threshold: remoteThreshold ? Number(remoteThreshold) : undefined,
featuresEnabled: localSafe?.currentVersion ? enabledFeatures(localSafe.currentVersion) : localSafe?.featuresEnabled,
}
dispatch(updateSafe(updatedSafe))
if (!remoteOwners.length) {
return
}
// If the remote owners does not contain a local address, we remove that local owner
localOwners.forEach((localAddress) => {
const remoteOwnerIndex = remoteOwners.findIndex((remoteAddress) => sameAddress(remoteAddress, localAddress))

View File

@ -32,6 +32,8 @@ const watchedActions = [
ADD_QUEUED_TRANSACTIONS,
]
const LAST_TIME_USED_LOGGED_IN_ID = 'LAST_TIME_USED_LOGGED_IN_ID'
const sendAwaitingTransactionNotification = async (
dispatch,
safeAddress,
@ -39,7 +41,6 @@ const sendAwaitingTransactionNotification = async (
notificationKey,
notificationClickedCb,
) => {
const LAST_TIME_USED_LOGGED_IN_ID = 'LAST_TIME_USED_LOGGED_IN_ID'
if (!dispatch || !safeAddress || !awaitingTxsSubmissionDateList || !notificationKey) {
return
}
@ -48,25 +49,25 @@ const sendAwaitingTransactionNotification = async (
}
let lastTimeUserLoggedInForSafes = (await loadFromStorage<Record<string, string>>(LAST_TIME_USED_LOGGED_IN_ID)) || {}
const lastTimeUserLoggedIn =
lastTimeUserLoggedInForSafes && lastTimeUserLoggedInForSafes[safeAddress]
? lastTimeUserLoggedInForSafes[safeAddress]
: null
const lastTimeUserLoggedIn = lastTimeUserLoggedInForSafes[safeAddress]
? lastTimeUserLoggedInForSafes[safeAddress]
: null
const filteredDuplicatedAwaitingTxList = awaitingTxsSubmissionDateList.filter((submissionDate) => {
return lastTimeUserLoggedIn ? new Date(submissionDate) > new Date(lastTimeUserLoggedIn) : true
})
if (filteredDuplicatedAwaitingTxList.length === 0) {
if (!filteredDuplicatedAwaitingTxList.length) {
return
}
dispatch(
enqueueSnackbar(enhanceSnackbarForAction(NOTIFICATIONS.TX_WAITING_MSG, notificationKey, notificationClickedCb)),
)
lastTimeUserLoggedInForSafes = {
...lastTimeUserLoggedInForSafes,
[safeAddress]: lastTimeUserLoggedIn || new Date(),
[safeAddress]: new Date(),
}
await saveToStorage(LAST_TIME_USED_LOGGED_IN_ID, lastTimeUserLoggedInForSafes)
}

View File

@ -11,7 +11,7 @@ export const gatewayTransactions = (state: AppReduxState): AppReduxState['gatewa
return state[GATEWAY_TRANSACTIONS_ID]
}
export const historyTransactions = createSelector(
export const historyTransactions = createHashBasedSelector(
gatewayTransactions,
safeParamAddressFromStateSelector,
(gatewayTransactions, safeAddress): StoreStructure['history'] | undefined => {

View File

@ -8,6 +8,6 @@ import { AppReduxState } from 'src/store'
export const createIsEqualSelector = createSelectorCreator(defaultMemoize, isEqual)
const hashFn = (gatewayTransactions: AppReduxState['gatewayTransactions'], safeAddress: string): string =>
hash(gatewayTransactions[safeAddress])
hash(gatewayTransactions[safeAddress] ?? {})
export const createHashBasedSelector = createSelectorCreator(memoize as any, hashFn)

View File

@ -1,4 +1,5 @@
import { BigNumber } from 'bignumber.js'
import { getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts'
import { calculateGasOf, EMPTY_DATA } from 'src/logic/wallets/ethTransactions'
import { getWeb3, web3ReadOnly } from 'src/logic/wallets/getWeb3'
@ -12,6 +13,12 @@ import { sameString } from 'src/utils/strings'
// 21000 - additional gas costs (e.g. base tx costs, transfer costs)
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 => {
@ -148,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)
@ -175,32 +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 + 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,
@ -213,26 +229,36 @@ 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 dataGasEstimation = parseRequiredTxGasResponse(estimateData)
// 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) {
console.info('Error calculating tx gas estimation', error.message)
@ -279,6 +305,7 @@ export const estimateGasForTransactionExecution = async ({
txRecipient,
txAmount,
operation,
safeTxGas,
)
console.info(`Gas estimation successfully finished with gas amount: ${gasEstimation}`)
return gasEstimation

View File

@ -46,7 +46,7 @@ const calculateBodyFrom = async (
export const buildTxServiceUrl = (safeAddress: string): string => {
const address = checksumAddress(safeAddress)
return `${getSafeServiceBaseUrl(address)}/transactions/?has_confirmations=True`
return `${getSafeServiceBaseUrl(address)}/multisig-transactions/?has_confirmations=True`
}
const SUCCESS_STATUS = 201 // CREATED status

View File

@ -11,10 +11,10 @@ export type TokenResult = {
type: string
}
export const fetchErc20AndErc721AssetsList = async (): Promise<AxiosResponse<{ results: TokenResult[] }>> => {
export const fetchErc20AndErc721AssetsList = (): Promise<AxiosResponse<{ results: TokenResult[] }>> => {
const url = getTokensServiceBaseUrl()
return axios.get<{ results: TokenResult[] }>(`${url}/`, {
return axios.get<{ results: TokenResult[] }, AxiosResponse<{ results: TokenResult[] }>>(`${url}/`, {
params: {
limit: 3000,
},

View File

@ -20,5 +20,5 @@ export const fetchSafeCollectibles = async (safeAddress: string): Promise<AxiosR
const address = checksumAddress(safeAddress)
const url = `${getSafeServiceBaseUrl(address)}/collectibles/`
return axios.get(url)
return axios.get<CollectibleResult[], AxiosResponse<CollectibleResult[]>>(url)
}

View File

@ -1,6 +1,5 @@
import { backOff } from 'exponential-backoff'
import { List, Map } from 'immutable'
import { batch } from 'react-redux'
import { Dispatch } from 'redux'
import {
@ -84,11 +83,9 @@ const fetchSafeTokens = (safeAddress: string) => async (
balances.keySeq().toSet().subtract(blacklistedTokens),
)
batch(() => {
dispatch(updateSafe({ address: safeAddress, activeTokens, balances, ethBalance }))
dispatch(setCurrencyBalances(safeAddress, currencyList))
dispatch(addTokens(tokens))
})
dispatch(updateSafe({ address: safeAddress, activeTokens, balances, ethBalance }))
dispatch(setCurrencyBalances(safeAddress, currencyList))
dispatch(addTokens(tokens))
} catch (err) {
console.error('Error fetching active token list', err)
}

View File

@ -17,10 +17,8 @@ export const WALLET_PROVIDER = {
PORTIS: 'PORTIS',
FORTMATIC: 'FORTMATIC',
SQUARELINK: 'SQUARELINK',
UNILOGIN: 'UNILOGIN',
WALLETCONNECT: 'WALLETCONNECT',
OPERA: 'OPERA',
DAPPER: 'DAPPER',
WALLETLINK: 'WALLETLINK',
AUTHEREUM: 'AUTHEREUM',
LEDGER: 'LEDGER',

View File

@ -39,7 +39,6 @@ const wallets: Wallet[] = [
LedgerTransport: (window as any).TransportNodeHid,
},
{ walletName: WALLETS.TRUST, preferred: true, desktop: false },
{ walletName: WALLETS.DAPPER, desktop: false },
{
walletName: WALLETS.LATTICE,
rpcUrl,
@ -58,7 +57,6 @@ const wallets: Wallet[] = [
},
{ walletName: WALLETS.AUTHEREUM, desktop: false },
{ walletName: WALLETS.TORUS, desktop: true },
{ walletName: WALLETS.UNILOGIN, desktop: true },
{ walletName: WALLETS.COINBASE, desktop: false },
{ walletName: WALLETS.WALLET_LINK, rpcUrl, desktop: false },
{ walletName: WALLETS.OPERA, desktop: false },

View File

@ -1,5 +1,6 @@
import InputAdornment from '@material-ui/core/InputAdornment'
import MenuItem from '@material-ui/core/MenuItem'
import { Icon, Link, Text } from '@gnosis.pm/safe-react-components'
import { makeStyles } from '@material-ui/core/styles'
import CheckCircle from '@material-ui/icons/CheckCircle'
import * as React from 'react'
@ -159,6 +160,18 @@ const SafeOwnersForm = (props): React.ReactElement => {
Add additional owners (e.g. wallets of your teammates) and specify how many of them have to confirm a
transaction before it gets executed. You can also add/remove owners and change the signature threshold after
your Safe is created.
<Link
href="https://help.gnosis-safe.io/en/articles/4772567-what-gnosis-safe-setup-should-i-use"
target="_blank"
className={classes.link}
rel="noreferrer"
title="Learn about which Safe setup to use"
>
<Text size="lg" as="span" color="primary">
Learn about which Safe setup to use
</Text>
<Icon size="sm" type="externalLink" color="primary" />
</Link>
</Paragraph>
</Block>
<Hairline />

View File

@ -1,4 +1,4 @@
import { disabled, extraSmallFontSize, lg, md, screenSm, sm } from 'src/theme/variables'
import { disabled, extraSmallFontSize, lg, md, screenSm, sm, xs } from 'src/theme/variables'
import { createStyles } from '@material-ui/core'
export const styles = createStyles({
@ -8,6 +8,16 @@ export const styles = createStyles({
title: {
padding: `${md} ${lg}`,
},
link: {
paddingLeft: `${xs}`,
'& svg': {
position: 'relative',
top: '1px',
left: `${xs}`,
height: '14px',
width: '14px',
},
},
owner: {
flexDirection: 'column',
marginTop: '12px',

View File

@ -5,6 +5,7 @@ import styled from 'styled-components'
import { SafeApp } from 'src/routes/safe/components/Apps/types.d'
import GnoForm from 'src/components/forms/GnoForm'
import Img from 'src/components/layout/Img'
import { Icon, Link, Text } from '@gnosis.pm/safe-react-components'
import AppAgreement from './AppAgreement'
import AppUrl, { AppInfoUpdater, appUrlResolver } from './AppUrl'
@ -29,6 +30,16 @@ const AppInfo = styled.div`
margin-right: 10px;
}
`
const AppDocsInfo = styled.div`
display: flex;
margin-bottom: 10px;
flex-direction: column;
svg {
position: relative;
top: 4px;
left: 4px;
}
`
export interface AddAppFormValues {
appUrl: string
@ -66,6 +77,22 @@ const AddApp = ({ appList, closeModal }: AddAppProps): ReactElement => {
<GnoForm decorators={[appUrlResolver]} initialValues={INITIAL_VALUES} onSubmit={handleSubmit} testId={FORM_ID}>
{() => (
<>
<AppDocsInfo>
<Text size="xl" as="span" color="secondary">
Safe Apps are third-party extensions.
</Text>
<Link
href="https://docs.gnosis.io/safe/docs/sdks_safe_apps/"
target="_blank"
rel="noreferrer"
title="Learn more about building Safe Apps"
>
<Text size="xl" as="span" color="primary">
Learn more about building Safe Apps.
</Text>
<Icon size="sm" type="externalLink" color="primary" />
</Link>
</AppDocsInfo>
<AppUrl appList={appList} />
{/* Fetch app from url and return a SafeApp */}

View File

@ -119,7 +119,7 @@ const AppFrame = ({ appUrl }: Props): React.ReactElement => {
useEffect(() => {
if (appIsLoading) {
timer.current = setTimeout(() => {
timer.current = window.setTimeout(() => {
setAppTimeout(true)
}, TIMEOUT)
} else {

View File

@ -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}/QmYCzVxP4v8dX92d3nPvnxCksmsjdbKEPMzDHwudK2SALE`,
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],
},
// 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,
@ -113,7 +131,7 @@ export const staticAppsList: Array<StaticAppInfo> = [
},
// Wallet-Connect
{
url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmT3VxxfFtfEcvq8AeaoFAyUGxePRa2zisvnxLTrQXU5Uf`,
url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmRMGTA5ARMwfhYbdmK83zzMd13NnEUKFJSZEgEjKa8YQm`,
disabled: false,
networks: [
ETHEREUM_NETWORK.MAINNET,

View File

@ -1,7 +1,7 @@
import React from 'react'
import { useField, useForm } from 'react-final-form'
import TextareaField from 'src/components/forms/TextareaField'
import { TextAreaField } from 'src/components/forms/TextAreaField'
import { mustBeEthereumAddress, mustBeEthereumContractAddress } from 'src/components/forms/validator'
import Col from 'src/components/layout/Col'
import Row from 'src/components/layout/Row'
@ -54,7 +54,7 @@ const ContractABI = (): React.ReactElement => {
return (
<Row margin="sm">
<Col>
<TextareaField name="abi" placeholder="ABI*" text="ABI*" type="text" validate={hasUsefulMethods} />
<TextAreaField name="abi" placeholder="ABI*" text="ABI*" type="text" validate={hasUsefulMethods} />
</Col>
</Row>
)

View File

@ -1,6 +1,6 @@
import React from 'react'
import TextareaField from 'src/components/forms/TextareaField'
import { TextAreaField } from 'src/components/forms/TextAreaField'
import {
isAddress,
isBoolean,
@ -46,7 +46,7 @@ const typePlaceholder = (text: string, type: string): string => {
}
const ArrayTypeInput = ({ name, text, type }: { name: string; text: string; type: string }): React.ReactElement => (
<TextareaField name={name} placeholder={typePlaceholder(text, type)} text={text} type="text" validate={validator} />
<TextAreaField name={name} placeholder={typePlaceholder(text, type)} text={text} type="text" validate={validator} />
)
export default ArrayTypeInput

View File

@ -10,7 +10,7 @@ import QRIcon from 'src/assets/icons/qrcode.svg'
import CopyBtn from 'src/components/CopyBtn'
import Field from 'src/components/forms/Field'
import GnoForm from 'src/components/forms/GnoForm'
import TextareaField from 'src/components/forms/TextareaField'
import { TextAreaField } from 'src/components/forms/TextAreaField'
import TextField from 'src/components/forms/TextField'
import { composeValidators, maxValue, minValue, mustBeFloat } from 'src/components/forms/validator'
import Identicon from 'src/components/Identicon'
@ -247,7 +247,7 @@ const SendCustomTx: React.FC<Props> = ({ initialValues, onClose, onNext, contrac
</Row>
<Row margin="sm">
<Col>
<TextareaField
<TextAreaField
name="data"
placeholder="Data (hex encoded)*"
text="Data (hex encoded)*"

View File

@ -70,7 +70,7 @@ const ContractInteraction: React.FC<ContractInteractionProps> = ({
const handleSubmit = async (
{ contractAddress, selectedMethod, value, ...values },
submit = true,
): Promise<void | any> => {
): Promise<void | Record<string, string>> => {
if (value || (contractAddress && selectedMethod)) {
try {
const txObject = createTxObject(selectedMethod, contractAddress, values)

View File

@ -6,6 +6,7 @@ import { AbiItemExtended } from 'src/logic/contractInteraction/sources/ABIServic
import { getAddressFromDomain, getWeb3 } from 'src/logic/wallets/getWeb3'
import { TransactionReviewType } from 'src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/Review'
import { isValidCryptoDomainName, isValidEnsName } from 'src/logic/wallets/ethAddresses'
import { BigNumber } from 'bignumber.js'
export const NO_CONTRACT = 'no contract'
@ -67,7 +68,13 @@ export const isByte = (type: string): boolean => type.indexOf('byte') === 0
export const isArrayParameter = (parameter: string): boolean => /(\[\d*])+$/.test(parameter)
export const getParsedJSONOrArrayFromString = (parameter: string): (string | number)[] | null => {
try {
return JSON.parse(parameter)
const arrayResult = JSON.parse(parameter)
return arrayResult.map((value) => {
if (Number.isInteger(value)) {
return new BigNumber(value).toString()
}
return value
})
} catch (err) {
return null
}
@ -101,7 +108,7 @@ export const createTxObject = (
values: Record<string, string>,
): ContractSendMethod => {
const web3 = getWeb3()
const contract: any = new web3.eth.Contract([method], contractAddress)
const contract = new web3.eth.Contract([method], contractAddress)
const { inputs, name = '', signatureHash } = method
const args = inputs?.map(extractMethodArgs(signatureHash, values)) || []

View File

@ -114,6 +114,7 @@ const ReviewSendFundsTx = ({ onClose, onPrev, tx }: ReviewTxProps): React.ReactE
txData: data,
txRecipient,
txType: tx.txType,
txAmount: txValue,
safeTxGas: manualSafeTxGas,
manualGasPrice,
})

View File

@ -0,0 +1,25 @@
import { Loader } from '@gnosis.pm/safe-react-components'
import React, { ReactElement } from 'react'
import { usePagedHistoryTransactions } from './hooks/usePagedHistoryTransactions'
import { Centered } from './styled'
import { HistoryTxList } from './HistoryTxList'
import { TxsInfiniteScroll } from './TxsInfiniteScroll'
export const HistoryTransactions = (): ReactElement => {
const { count, hasMore, next, transactions, isLoading } = usePagedHistoryTransactions()
if (count === 0) {
return (
<Centered>
<Loader size="md" />
</Centered>
)
}
return (
<TxsInfiniteScroll next={next} hasMore={hasMore} isLoading={isLoading}>
<HistoryTxList transactions={transactions} />
</TxsInfiniteScroll>
)
}

View File

@ -1,46 +1,35 @@
import { Loader } from '@gnosis.pm/safe-react-components'
import React, { ReactElement } from 'react'
import React, { ReactElement, useContext } from 'react'
import { InfiniteScroll, SCROLLABLE_TARGET_ID } from 'src/components/InfiniteScroll'
import { usePagedHistoryTransactions } from './hooks/usePagedHistoryTransactions'
import {
SubTitle,
ScrollableTransactionsContainer,
StyledTransactions,
StyledTransactionsGroup,
Centered,
} from './styled'
import { TransactionDetails } from 'src/logic/safe/store/models/types/gateway.d'
import { TxsInfiniteScrollContext } from 'src/routes/safe/components/Transactions/GatewayTransactions/TxsInfiniteScroll'
import { formatWithSchema } from 'src/utils/date'
import { sameString } from 'src/utils/strings'
import { StyledTransactions, StyledTransactionsGroup, SubTitle } from './styled'
import { TxHistoryRow } from './TxHistoryRow'
import { TxLocationContext } from './TxLocationProvider'
import { formatWithSchema } from 'src/utils/date'
export const HistoryTxList = (): ReactElement => {
const { count, hasMore, next, transactions } = usePagedHistoryTransactions()
export const HistoryTxList = ({ transactions }: { transactions: TransactionDetails['transactions'] }): ReactElement => {
const { lastItemId, setLastItemId } = useContext(TxsInfiniteScrollContext)
if (count === 0) {
return (
<Centered>
<Loader size="md" />
</Centered>
)
const [, lastTransactionsGroup] = transactions[transactions.length - 1]
const lastTransaction = lastTransactionsGroup[lastTransactionsGroup.length - 1]
if (!sameString(lastItemId, lastTransaction.id)) {
setLastItemId(lastTransaction.id)
}
return (
<TxLocationContext.Provider value={{ txLocation: 'history' }}>
<ScrollableTransactionsContainer id={SCROLLABLE_TARGET_ID}>
<InfiniteScroll dataLength={transactions.length} next={next} hasMore={hasMore}>
{transactions?.map(([timestamp, txs]) => (
<StyledTransactionsGroup key={timestamp}>
<SubTitle size="lg">{formatWithSchema(Number(timestamp), 'MMM d, yyyy')}</SubTitle>
<StyledTransactions>
{txs.map((transaction) => (
<TxHistoryRow key={transaction.id} transaction={transaction} />
))}
</StyledTransactions>
</StyledTransactionsGroup>
))}
</InfiniteScroll>
</ScrollableTransactionsContainer>
{transactions?.map(([timestamp, txs]) => (
<StyledTransactionsGroup key={timestamp}>
<SubTitle size="lg">{formatWithSchema(Number(timestamp), 'MMM d, yyyy')}</SubTitle>
<StyledTransactions>
{txs.map((transaction) => (
<TxHistoryRow key={transaction.id} transaction={transaction} />
))}
</StyledTransactions>
</StyledTransactionsGroup>
))}
</TxLocationContext.Provider>
)
}

View File

@ -2,15 +2,15 @@ import { Loader, Title } from '@gnosis.pm/safe-react-components'
import React, { ReactElement } from 'react'
import style from 'styled-components'
import { InfiniteScroll, SCROLLABLE_TARGET_ID } from 'src/components/InfiniteScroll'
import Img from 'src/components/layout/Img'
import { usePagedQueuedTransactions } from './hooks/usePagedQueuedTransactions'
import { ActionModal } from './ActionModal'
import { TxActionProvider } from './TxActionProvider'
import { TxLocationContext } from './TxLocationProvider'
import { QueueTxList } from './QueueTxList'
import { ScrollableTransactionsContainer, Centered } from './styled'
import NoTransactionsImage from './assets/no-transactions.svg'
import { usePagedQueuedTransactions } from './hooks/usePagedQueuedTransactions'
import { QueueTxList } from './QueueTxList'
import { Centered } from './styled'
import { TxActionProvider } from './TxActionProvider'
import { TxsInfiniteScroll } from './TxsInfiniteScroll'
import { TxLocationContext } from './TxLocationProvider'
const NoTransactions = style.div`
display: flex;
@ -19,11 +19,11 @@ const NoTransactions = style.div`
`
export const QueueTransactions = (): ReactElement => {
const { count, loading, hasMore, next, transactions } = usePagedQueuedTransactions()
const { count, isLoading, hasMore, next, transactions } = usePagedQueuedTransactions()
// `loading` is, actually `!transactions`
// added the `transaction` verification to prevent `Object is possibly 'undefined'` error
if (loading || !transactions) {
if (isLoading || !transactions) {
return (
<Centered>
<Loader size="md" />
@ -42,19 +42,17 @@ export const QueueTransactions = (): ReactElement => {
return (
<TxActionProvider>
<ScrollableTransactionsContainer id={SCROLLABLE_TARGET_ID}>
<InfiniteScroll dataLength={count} next={next} hasMore={hasMore}>
{/* Next list */}
<TxLocationContext.Provider value={{ txLocation: 'queued.next' }}>
{transactions.next.count !== 0 && <QueueTxList transactions={transactions.next.transactions} />}
</TxLocationContext.Provider>
<TxsInfiniteScroll next={next} hasMore={hasMore} isLoading={isLoading}>
{/* Next list */}
<TxLocationContext.Provider value={{ txLocation: 'queued.next' }}>
{transactions.next.count !== 0 && <QueueTxList transactions={transactions.next.transactions} />}
</TxLocationContext.Provider>
{/* Queue list */}
<TxLocationContext.Provider value={{ txLocation: 'queued.queued' }}>
{transactions.queue.count !== 0 && <QueueTxList transactions={transactions.queue.transactions} />}
</TxLocationContext.Provider>
</InfiniteScroll>
</ScrollableTransactionsContainer>
{/* Queue list */}
<TxLocationContext.Provider value={{ txLocation: 'queued.queued' }}>
{transactions.queue.count !== 0 && <QueueTxList transactions={transactions.queue.transactions} />}
</TxLocationContext.Provider>
</TxsInfiniteScroll>
<ActionModal />
</TxActionProvider>
)

View File

@ -2,6 +2,7 @@ import { Icon, Link, Text } from '@gnosis.pm/safe-react-components'
import React, { Fragment, ReactElement, useContext } from 'react'
import { Transaction, TransactionDetails } from 'src/logic/safe/store/models/types/gateway.d'
import { sameString } from 'src/utils/strings'
import {
DisclaimerContainer,
GroupedTransactions,
@ -14,6 +15,7 @@ import {
import { TxHoverProvider } from './TxHoverProvider'
import { TxLocationContext } from './TxLocationProvider'
import { TxQueueRow } from './TxQueueRow'
import { TxsInfiniteScrollContext } from './TxsInfiniteScroll'
const TreeView = ({ firstElement }: { firstElement: boolean }): ReactElement => {
return <p className="tree-lines">{firstElement ? <span className="first-node" /> : null}</p>
@ -52,8 +54,8 @@ type QueueTransactionProps = {
transactions: Transaction[]
}
const QueueTransaction = ({ nonce, transactions }: QueueTransactionProps): ReactElement => {
return transactions.length > 1 ? (
const QueueTransaction = ({ nonce, transactions }: QueueTransactionProps): ReactElement =>
transactions.length > 1 ? (
<GroupedTransactionsCard>
<TxHoverProvider>
<Disclaimer nonce={nonce} />
@ -70,7 +72,6 @@ const QueueTransaction = ({ nonce, transactions }: QueueTransactionProps): React
) : (
<TxQueueRow transaction={transactions[0]} />
)
}
type QueueTxListProps = {
transactions: TransactionDetails['transactions']
@ -80,6 +81,16 @@ export const QueueTxList = ({ transactions }: QueueTxListProps): ReactElement =>
const { txLocation } = useContext(TxLocationContext)
const title = txLocation === 'queued.next' ? 'NEXT TRANSACTION' : 'QUEUE'
const { lastItemId, setLastItemId } = useContext(TxsInfiniteScrollContext)
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)
}
}
return (
<StyledTransactionsGroup>
<SubTitle size="lg">{title}</SubTitle>

View File

@ -1,8 +1,6 @@
import { Dot, IconText as IconTextSrc, Text } from '@gnosis.pm/safe-react-components'
import { Dot, IconText as IconTextSrc, Text, Tooltip } from '@gnosis.pm/safe-react-components'
import { ThemeColors } from '@gnosis.pm/safe-react-components/dist/theme'
import { Tooltip } from '@material-ui/core'
import CircularProgress from '@material-ui/core/CircularProgress'
import { createStyles, makeStyles } from '@material-ui/core/styles'
import React, { ReactElement, useContext, useRef } from 'react'
import styled from 'styled-components'
@ -13,7 +11,7 @@ import {
isSettingsChangeTxInfo,
Transaction,
} from 'src/logic/safe/store/models/types/gateway.d'
import { TxCollapsedActions } from 'src/routes/safe/components/Transactions/GatewayTransactions/TxCollapsedActions'
import { TxCollapsedActions } from './TxCollapsedActions'
import { formatDateTime, formatTime, formatTimeInWords } from 'src/utils/date'
import { KNOWN_MODULES } from 'src/utils/constants'
import { sameString } from 'src/utils/strings'
@ -23,6 +21,7 @@ import { TransactionStatusProps } from './hooks/useTransactionStatus'
import { TxTypeProps } from './hooks/useTransactionType'
import { StyledGroupedTransactions, StyledTransaction } from './styled'
import { TokenTransferAmount } from './TokenTransferAmount'
import { TxsInfiniteScrollContext } from './TxsInfiniteScroll'
import { TxLocationContext } from './TxLocationProvider'
import { CalculatedVotes } from './TxQueueCollapsed'
@ -86,27 +85,12 @@ const IconText = styled(IconTextSrc)`
}
`
const useTooltipStyles = makeStyles(
createStyles(() => ({
arrow: {
color: 'white',
},
tooltip: {
backgroundColor: 'white',
color: 'rgba(0, 0, 0, 0.87)',
boxShadow: '#00000026 0 2px 4px 0',
fontSize: '14px',
lineHeight: '14px',
},
})),
)
const TooltipContent = styled.div`
width: max-content;
`
type TxCollapsedProps = {
transaction?: Transaction
transaction: Transaction
isGrouped?: boolean
nonce?: number
type: TxTypeProps
@ -129,6 +113,7 @@ export const TxCollapsed = ({
status,
}: TxCollapsedProps): ReactElement => {
const { txLocation } = useContext(TxLocationContext)
const { ref, lastItemId } = useContext(TxsInfiniteScrollContext)
const willBeReplaced = transaction?.txStatus === 'WILL_BE_REPLACED' ? ' will-be-replaced' : ''
@ -146,12 +131,11 @@ export const TxCollapsed = ({
const txCollapsedInfo = <div className={'tx-info' + willBeReplaced}>{info && <TxInfo info={info} />}</div>
const tooltipStyles = useTooltipStyles()
const timestamp = useRef<HTMLDivElement | null>(null)
const txCollapsedTime = (
<div className={'tx-time' + willBeReplaced}>
<Tooltip classes={tooltipStyles} title={formatDateTime(time)} arrow>
<Tooltip title={formatDateTime(time)} arrow backgroundColor="white" size="lg">
<TooltipContent ref={timestamp}>
<Text size="xl">{txLocation === 'history' ? formatTime(time) : formatTimeInWords(time)}</Text>
</TooltipContent>
@ -179,8 +163,9 @@ export const TxCollapsed = ({
</div>
)
// attaching ref to a div element as it was causing troubles to add a `ref` to a FunctionComponent
const txCollapsedStatus = (
<div className="tx-status">
<div className="tx-status" ref={sameString(lastItemId, transaction.id) ? ref : null}>
{transaction?.txStatus === 'PENDING' || transaction?.txStatus === 'PENDING_FAILED' ? (
<CircularProgressPainter color={status.color}>
<CircularProgress size={14} color="inherit" />

View File

@ -1,26 +1,11 @@
import { Icon, theme } from '@gnosis.pm/safe-react-components'
import { Tooltip as TooltipMui } from '@material-ui/core'
import { Icon, Tooltip } from '@gnosis.pm/safe-react-components'
import { default as MuiIconButton } from '@material-ui/core/IconButton'
import { withStyles } from '@material-ui/core/styles'
import React, { ReactElement } from 'react'
import styled from 'styled-components'
import { Transaction } from 'src/logic/safe/store/models/types/gateway.d'
import { useActionButtonsHandlers } from './hooks/useActionButtonsHandlers'
const Tooltip = withStyles(() => ({
popper: {
zIndex: 2001,
},
tooltip: {
marginBottom: '4px',
backgroundColor: theme.colors.overlay.color,
border: `1px solid ${theme.colors.icon}`,
boxShadow: `1px 2px 4px ${theme.colors.shadow.color}14`,
color: theme.colors.text,
},
}))(TooltipMui)
const IconButton = styled(MuiIconButton)`
padding: 8px !important;
@ -46,8 +31,8 @@ export const TxCollapsedActions = ({ transaction }: TxCollapsedActionsProps): Re
return (
<>
{
<Tooltip title={transaction.txStatus === 'AWAITING_EXECUTION' ? 'Execute' : 'Confirm'} placement="top">
<Tooltip title={transaction.txStatus === 'AWAITING_EXECUTION' ? 'Execute' : 'Confirm'} placement="top">
<span>
<IconButton
size="small"
type="button"
@ -58,13 +43,15 @@ export const TxCollapsedActions = ({ transaction }: TxCollapsedActionsProps): Re
>
<Icon type={transaction.txStatus === 'AWAITING_EXECUTION' ? 'rocket' : 'check'} color="primary" size="sm" />
</IconButton>
</Tooltip>
}
</span>
</Tooltip>
{canCancel && (
<Tooltip title="Cancel" placement="top">
<IconButton size="small" type="button" onClick={handleCancelButtonClick} disabled={isPending}>
<Icon type="circleCross" color="error" size="sm" />
</IconButton>
<span>
<IconButton size="small" type="button" onClick={handleCancelButtonClick} disabled={isPending}>
<Icon type="circleCross" color="error" size="sm" />
</IconButton>
</span>
</Tooltip>
)}
</>

View File

@ -12,5 +12,14 @@ export const TxHistoryCollapsed = ({ transaction }: { transaction: Transaction }
const info = useAssetInfo(transaction.txInfo)
const status = useTransactionStatus(transaction)
return <TxCollapsed nonce={nonce} type={type} info={info} time={transaction.timestamp} status={status} />
return (
<TxCollapsed
nonce={nonce}
type={type}
info={info}
time={transaction.timestamp}
status={status}
transaction={transaction}
/>
)
}

View File

@ -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} />

View File

@ -0,0 +1,27 @@
import { Loader } from '@gnosis.pm/safe-react-components'
import React, { ReactElement, ReactNode } from 'react'
import { INFINITE_SCROLL_CONTAINER, InfiniteScroll } from 'src/components/InfiniteScroll'
import { HorizontallyCentered, ScrollableTransactionsContainer } from './styled'
type TxsInfiniteScrollProps = {
children: ReactNode
next: () => Promise<void>
hasMore: boolean
isLoading: boolean
}
export const TxsInfiniteScroll = ({ children, next, hasMore, isLoading }: TxsInfiniteScrollProps): ReactElement => {
return (
<InfiniteScroll next={next} hasMore={hasMore}>
<ScrollableTransactionsContainer id={INFINITE_SCROLL_CONTAINER}>
{children}
<HorizontallyCentered isVisible={isLoading}>
<Loader size="md" />
</HorizontallyCentered>
</ScrollableTransactionsContainer>
</InfiniteScroll>
)
}
export { InfiniteScrollContext as TxsInfiniteScrollContext } from 'src/components/InfiniteScroll'

View File

@ -1,4 +1,4 @@
import { useState } from 'react'
import { useCallback, useRef, useState } from 'react'
import { useDispatch, useSelector } from 'react-redux'
import { loadPagedHistoryTransactions } from 'src/logic/safe/store/actions/transactions/fetchTransactions/loadGatewayTransactions'
@ -12,20 +12,24 @@ type PagedTransactions = {
transactions: TransactionDetails['transactions']
hasMore: boolean
next: () => Promise<void>
isLoading: boolean
}
export const usePagedHistoryTransactions = (): PagedTransactions => {
const { count, transactions } = useHistoryTransactions()
const dispatch = useDispatch()
const safeAddress = useSelector(safeParamAddressFromStateSelector)
const dispatch = useRef(useDispatch())
const safeAddress = useRef(useSelector(safeParamAddressFromStateSelector))
const [hasMore, setHasMore] = useState(true)
const [isLoading, setIsLoading] = useState(false)
const next = async () => {
const results = await loadPagedHistoryTransactions(safeAddress)
const next = useCallback(async () => {
setIsLoading(true)
const results = await loadPagedHistoryTransactions(safeAddress.current)
if (!results) {
setHasMore(false)
setIsLoading(false)
return
}
@ -36,11 +40,12 @@ export const usePagedHistoryTransactions = (): PagedTransactions => {
}
if (values) {
dispatch(addHistoryTransactions({ safeAddress, values, isTail: true }))
dispatch.current(addHistoryTransactions({ safeAddress: safeAddress.current, values, isTail: true }))
} else {
setHasMore(false)
}
}
setIsLoading(false)
}, [])
return { count, transactions, hasMore, next }
return { count, transactions, hasMore, next, isLoading }
}

View File

@ -8,7 +8,7 @@ import { QueueTransactionsInfo, useQueueTransactions } from './useQueueTransacti
type PagedQueuedTransactions = {
count: number
loading: boolean
isLoading: boolean
transactions?: QueueTransactionsInfo
hasMore: boolean
next: () => Promise<void>
@ -46,7 +46,7 @@ export const usePagedQueuedTransactions = (): PagedQueuedTransactions => {
count = transactions.next.count + transactions.queue.count
}
const loading = typeof transactions === 'undefined' || typeof count === 'undefined'
const isLoading = typeof transactions === 'undefined' || typeof count === 'undefined'
return { count, loading, transactions, hasMore, next: nextPage }
return { count, isLoading, transactions, hasMore, next: nextPage }
}

View File

@ -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 = {

View File

@ -3,7 +3,7 @@ import { Item } from '@gnosis.pm/safe-react-components/dist/navigation/Tab'
import React, { ReactElement, useState } from 'react'
import styled from 'styled-components'
import { HistoryTxList } from './HistoryTxList'
import { HistoryTransactions } from './HistoryTransactions'
import { QueueTransactions } from './QueueTransactions'
import { Breadcrumb, ContentWrapper, Wrapper } from './styled'
@ -27,7 +27,7 @@ const GatewayTransactions = (): ReactElement => {
<Tab items={items} onChange={setTab} selectedTab={tab} />
<ContentWrapper>
{tab === 'queue' && <QueueTransactions />}
{tab === 'history' && <HistoryTxList />}
{tab === 'history' && <HistoryTransactions />}
</ContentWrapper>
</Wrapper>
)

View File

@ -91,7 +91,6 @@ export const StyledTransactions = styled.div`
overflow: hidden;
width: 100%;
// '^' to prevent applying rules to the 'Actions' accordion components
& > .MuiAccordion-root {
&:first-child {
border-top: none;
@ -485,6 +484,11 @@ export const Centered = styled.div<{ padding?: number }>`
align-items: center;
`
export const HorizontallyCentered = styled(Centered)<{ isVisible: boolean }>`
visibility: ${({ isVisible }) => (isVisible ? 'visible' : 'hidden')};
height: auto;
`
export const StyledAccordionSummary = styled(AccordionSummary)`
height: 52px;
.tx-nonce {

View File

@ -88,11 +88,10 @@ export const useTransactionParameters = (props?: Props): TxParameters => {
}
}
const safeNonce = Number(props?.initialSafeNonce || 0)
if (!safeNonce) {
if (safeNonce === undefined) {
getSafeNonce()
}
}, [safeAddress, props?.initialSafeNonce])
}, [safeAddress, safeNonce])
return {
safeNonce,

4116
yarn.lock

File diff suppressed because it is too large Load Diff