Merge branch 'development' of github.com:gnosis/safe-react into development
This commit is contained in:
commit
5e1f380968
|
@ -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 }}
|
|
@ -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 }}
|
||||
|
|
@ -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 }}
|
|
@ -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 }}
|
|
@ -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 }}
|
|
@ -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
|
|
@ -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()
|
||||
|
174
.travis.yml
174
.travis.yml
|
@ -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
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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
|
|
@ -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
|
|
@ -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"
|
|
@ -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>
|
||||
<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>
|
||||
}
|
||||
|
|
|
@ -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,70 @@ 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 {
|
||||
switch (assets.status) {
|
||||
case 'fulfilled':
|
||||
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)
|
||||
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':
|
||||
const {
|
||||
data: { results = [] },
|
||||
} = tokens.value
|
||||
collectibles.erc721Tokens = results
|
||||
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 extractAssets(assets: TokenResult[], nftTokens: NFTTokens): NFTAssets {
|
||||
const extractNFTAsset = (asset: TokenResult): NFTAsset => {
|
||||
const numberOfTokens = nftTokens.filter(({ assetAddress }) => assetAddress === asset.address).length
|
||||
static extractNFTAsset = (asset: TokenResult, nftTokens: NFTTokens): NFTAsset => {
|
||||
const mainAssetAddress = asset.address
|
||||
const numberOfTokens = nftTokens.filter(({ assetAddress }) => sameAddress(assetAddress, mainAssetAddress)).length
|
||||
|
||||
return {
|
||||
address: asset.address,
|
||||
address: mainAssetAddress,
|
||||
description: asset.name,
|
||||
image: asset.logoUri || NFTIcon,
|
||||
name: asset.name,
|
||||
numberOfTokens,
|
||||
slug: `${asset.address}_${asset.name}`,
|
||||
slug: `${mainAssetAddress}_${asset.name}`,
|
||||
symbol: asset.symbol,
|
||||
}
|
||||
}
|
||||
|
||||
return assets.reduce((acc, asset) => {
|
||||
static extractAssets(assets: TokenResult[], nftTokens: NFTTokens): NFTAssets {
|
||||
const extractedAssets = {}
|
||||
|
||||
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 +97,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 }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 }),
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
})
|
||||
} catch (error) {
|
||||
console.log('Error fetching collectibles:', error)
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import {
|
|||
estimateGasForTransactionCreation,
|
||||
estimateGasForTransactionExecution,
|
||||
MINIMUM_TRANSACTION_GAS,
|
||||
GAS_REQUIRED_PER_SIGNATURE,
|
||||
} from 'src/logic/safe/transactions/gas'
|
||||
import { fromTokenUnit } from 'src/logic/tokens/utils/humanReadableValue'
|
||||
import { formatAmount } from 'src/logic/tokens/utils/formatAmount'
|
||||
|
@ -229,12 +230,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 gasCost = fromTokenUnit(estimatedGasCosts, nativeCoin.decimals)
|
||||
const gasCostFormatted = formatAmount(gasCost)
|
||||
const gasLimit = (gasEstimation * 2 + MINIMUM_TRANSACTION_GAS).toString()
|
||||
const gasLimit = (gasEstimation * 2).toString()
|
||||
|
||||
let txEstimationExecutionStatus = EstimationStatus.SUCCESS
|
||||
|
||||
|
@ -257,7 +259,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 = MINIMUM_TRANSACTION_GAS + (threshold || 1) * GAS_REQUIRED_PER_SIGNATURE
|
||||
const gasCost = fromTokenUnit(gasEstimation, nativeCoin.decimals)
|
||||
const gasCostFormatted = formatAmount(gasCost)
|
||||
setGasEstimation({
|
||||
|
|
|
@ -28,7 +28,7 @@ export const useSafeScheduledUpdates = (safeLoaded: boolean, safeAddress?: strin
|
|||
})
|
||||
|
||||
if (mounted) {
|
||||
timer.current = setTimeout(() => {
|
||||
timer.current = window.setTimeout(() => {
|
||||
fetchSafeData(address)
|
||||
}, TIMEOUT * 3)
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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,8 @@ 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
|
||||
|
||||
// Receives the response data of the safe method requiredTxGas() and parses it to get the gas amount
|
||||
const parseRequiredTxGasResponse = (data: string): number => {
|
||||
|
@ -177,9 +180,10 @@ const calculateMinimumGasForTransaction = async (
|
|||
estimateData: string,
|
||||
txGasEstimation: number,
|
||||
dataGasEstimation: number,
|
||||
fixedGasCosts: number,
|
||||
): Promise<number> => {
|
||||
for (const additionalGas of additionalGasBatches) {
|
||||
const amountOfGasToTryTx = txGasEstimation + dataGasEstimation + additionalGas
|
||||
const amountOfGasToTryTx = txGasEstimation + dataGasEstimation + fixedGasCosts + additionalGas
|
||||
console.info(`Estimating transaction creation with gas amount: ${amountOfGasToTryTx}`)
|
||||
try {
|
||||
const estimation = await getGasEstimationTxResponse({
|
||||
|
@ -224,7 +228,13 @@ export const estimateGasForTransactionCreation = async (
|
|||
return gasEstimationResponse
|
||||
}
|
||||
|
||||
const threshold = await safeInstance.methods.getThreshold().call()
|
||||
|
||||
const dataGasEstimation = parseRequiredTxGasResponse(estimateData)
|
||||
// We add the minimum required gas for a transaction
|
||||
// TODO: This fix will be more accurate when we have a service for estimation.
|
||||
// This fix takes the safe threshold and multiplies it by GAS_REQUIRED_PER_SIGNATURE.
|
||||
const fixedGasCosts = MINIMUM_TRANSACTION_GAS + (Number(threshold) || 1) * GAS_REQUIRED_PER_SIGNATURE
|
||||
const additionalGasBatches = [0, 10000, 20000, 40000, 80000, 160000, 320000, 640000, 1280000, 2560000, 5120000]
|
||||
|
||||
return await calculateMinimumGasForTransaction(
|
||||
|
@ -233,6 +243,7 @@ export const estimateGasForTransactionCreation = async (
|
|||
estimateData,
|
||||
gasEstimationResponse,
|
||||
dataGasEstimation,
|
||||
fixedGasCosts,
|
||||
)
|
||||
} catch (error) {
|
||||
console.info('Error calculating tx gas estimation', error.message)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -16,9 +16,11 @@ export type CollectibleResult = {
|
|||
uri: string | null
|
||||
}
|
||||
|
||||
export const fetchSafeCollectibles = async (safeAddress: string): Promise<AxiosResponse<CollectibleResult[]>> => {
|
||||
export const fetchSafeCollectibles = async (
|
||||
safeAddress: string,
|
||||
): Promise<AxiosResponse<{ results: CollectibleResult[] }>> => {
|
||||
const address = checksumAddress(safeAddress)
|
||||
const url = `${getSafeServiceBaseUrl(address)}/collectibles/`
|
||||
|
||||
return axios.get(url)
|
||||
return axios.get<CollectibleResult[], AxiosResponse<{ results: CollectibleResult[] }>>(url)
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('Error fetching active token list', err)
|
||||
}
|
||||
|
|
|
@ -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 */}
|
||||
|
|
|
@ -119,7 +119,7 @@ const AppFrame = ({ appUrl }: Props): React.ReactElement => {
|
|||
|
||||
useEffect(() => {
|
||||
if (appIsLoading) {
|
||||
timer.current = setTimeout(() => {
|
||||
timer.current = window.setTimeout(() => {
|
||||
setAppTimeout(true)
|
||||
}, TIMEOUT)
|
||||
} else {
|
||||
|
|
|
@ -113,7 +113,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,
|
||||
|
@ -129,6 +129,12 @@ export const staticAppsList: Array<StaticAppInfo> = [
|
|||
disabled: false,
|
||||
networks: [ETHEREUM_NETWORK.MAINNET],
|
||||
},
|
||||
// Mushrooms finance
|
||||
{
|
||||
url: `${process.env.REACT_APP_IPFS_GATEWAY}/QmdRLqtVW9nkjZmQiryoBfpvbqXrqYT59EawZcF5WXoLCY`,
|
||||
disabled: false,
|
||||
networks: [ETHEREUM_NETWORK.MAINNET, ETHEREUM_NETWORK.RINKEBY],
|
||||
},
|
||||
]
|
||||
|
||||
export const getAppInfoFromOrigin = (origin: string): { url: string; name: string } | null => {
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -1,34 +1,25 @@
|
|||
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>
|
||||
|
@ -39,8 +30,6 @@ export const HistoryTxList = (): ReactElement => {
|
|||
</StyledTransactions>
|
||||
</StyledTransactionsGroup>
|
||||
))}
|
||||
</InfiniteScroll>
|
||||
</ScrollableTransactionsContainer>
|
||||
</TxLocationContext.Provider>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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,8 +42,7 @@ export const QueueTransactions = (): ReactElement => {
|
|||
|
||||
return (
|
||||
<TxActionProvider>
|
||||
<ScrollableTransactionsContainer id={SCROLLABLE_TARGET_ID}>
|
||||
<InfiniteScroll dataLength={count} next={next} hasMore={hasMore}>
|
||||
<TxsInfiniteScroll next={next} hasMore={hasMore} isLoading={isLoading}>
|
||||
{/* Next list */}
|
||||
<TxLocationContext.Provider value={{ txLocation: 'queued.next' }}>
|
||||
{transactions.next.count !== 0 && <QueueTxList transactions={transactions.next.transactions} />}
|
||||
|
@ -53,8 +52,7 @@ export const QueueTransactions = (): ReactElement => {
|
|||
<TxLocationContext.Provider value={{ txLocation: 'queued.queued' }}>
|
||||
{transactions.queue.count !== 0 && <QueueTxList transactions={transactions.queue.transactions} />}
|
||||
</TxLocationContext.Provider>
|
||||
</InfiniteScroll>
|
||||
</ScrollableTransactionsContainer>
|
||||
</TxsInfiniteScroll>
|
||||
<ActionModal />
|
||||
</TxActionProvider>
|
||||
)
|
||||
|
|
|
@ -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,14 @@ export const QueueTxList = ({ transactions }: QueueTxListProps): ReactElement =>
|
|||
const { txLocation } = useContext(TxLocationContext)
|
||||
const title = txLocation === 'queued.next' ? 'NEXT TRANSACTION' : 'QUEUE'
|
||||
|
||||
const { lastItemId, setLastItemId } = useContext(TxsInfiniteScrollContext)
|
||||
const [, lastTransactionsGroup] = transactions[transactions.length - 1]
|
||||
const lastTransaction = lastTransactionsGroup[lastTransactionsGroup.length - 1]
|
||||
|
||||
if (txLocation === 'queued.queued' && !sameString(lastItemId, lastTransaction.id)) {
|
||||
setLastItemId(lastTransaction.id)
|
||||
}
|
||||
|
||||
return (
|
||||
<StyledTransactionsGroup>
|
||||
<SubTitle size="lg">{title}</SubTitle>
|
||||
|
|
|
@ -11,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'
|
||||
|
@ -21,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'
|
||||
|
||||
|
@ -89,7 +90,7 @@ const TooltipContent = styled.div`
|
|||
`
|
||||
|
||||
type TxCollapsedProps = {
|
||||
transaction?: Transaction
|
||||
transaction: Transaction
|
||||
isGrouped?: boolean
|
||||
nonce?: number
|
||||
type: TxTypeProps
|
||||
|
@ -112,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' : ''
|
||||
|
||||
|
@ -161,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" />
|
||||
|
|
|
@ -32,6 +32,7 @@ export const TxCollapsedActions = ({ transaction }: TxCollapsedActionsProps): Re
|
|||
return (
|
||||
<>
|
||||
<Tooltip title={transaction.txStatus === 'AWAITING_EXECUTION' ? 'Execute' : 'Confirm'} placement="top">
|
||||
<span>
|
||||
<IconButton
|
||||
size="small"
|
||||
type="button"
|
||||
|
@ -42,12 +43,15 @@ export const TxCollapsedActions = ({ transaction }: TxCollapsedActionsProps): Re
|
|||
>
|
||||
<Icon type={transaction.txStatus === 'AWAITING_EXECUTION' ? 'rocket' : 'check'} color="primary" size="sm" />
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
{canCancel && (
|
||||
<Tooltip title="Cancel" placement="top">
|
||||
<span>
|
||||
<IconButton size="small" type="button" onClick={handleCancelButtonClick} disabled={isPending}>
|
||||
<Icon type="circleCross" color="error" size="sm" />
|
||||
</IconButton>
|
||||
</span>
|
||||
</Tooltip>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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'
|
|
@ -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,17 +12,20 @@ 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)
|
||||
|
@ -36,11 +39,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 }
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Reference in New Issue