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:
parent
a4e3c82ca2
commit
4f9fd13285
|
@ -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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
Binary file not shown.
Before Width: | Height: | Size: 1.2 KiB |
|
@ -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 |
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
}
|
||||
|
|
|
@ -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" />
|
||||
}
|
|
@ -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,
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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))
|
||||
})
|
||||
dispatch(addNftAssets(collectibles.nftAssets))
|
||||
dispatch(addNftTokens(collectibles.nftTokens))
|
||||
} catch (error) {
|
||||
console.log('Error fetching collectibles:', error)
|
||||
}
|
||||
|
|
|
@ -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({
|
||||
|
|
|
@ -28,7 +28,7 @@ export const useSafeScheduledUpdates = (safeLoaded: boolean, safeAddress?: strin
|
|||
})
|
||||
|
||||
if (mounted) {
|
||||
timer.current = setTimeout(() => {
|
||||
timer.current = window.setTimeout(() => {
|
||||
fetchSafeData(address)
|
||||
}, TIMEOUT * 3)
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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,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
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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 },
|
||||
|
|
|
@ -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 />
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)*"
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)) || []
|
||||
|
||||
|
|
|
@ -114,6 +114,7 @@ const ReviewSendFundsTx = ({ onClose, onPrev, tx }: ReviewTxProps): React.ReactE
|
|||
txData: data,
|
||||
txRecipient,
|
||||
txType: tx.txType,
|
||||
txAmount: txValue,
|
||||
safeTxGas: manualSafeTxGas,
|
||||
manualGasPrice,
|
||||
})
|
||||
|
|
|
@ -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,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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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" />
|
||||
|
|
|
@ -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>
|
||||
)}
|
||||
</>
|
||||
|
|
|
@ -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}
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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} />
|
||||
|
|
|
@ -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,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 }
|
||||
}
|
||||
|
|
|
@ -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 }
|
||||
}
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue