diff --git a/.github/actions/release/action.yml b/.github/actions/release/action.yml index f349231f6..662802f11 100644 --- a/.github/actions/release/action.yml +++ b/.github/actions/release/action.yml @@ -33,6 +33,12 @@ inputs: react-app-mmi-environment: description: MMI environment required: true + react-app-walletconnect-project-id: + description: WalletConnect Project ID + required: true + react-app-ramp-api: + description: Ramp network API Key + required: true runs: using: composite @@ -63,6 +69,7 @@ runs: REACT_APP_MMI_BACKEND_BASE_URL: ${{ inputs.react-app-mmi-backend-base-url }} REACT_APP_MMI_ENVIRONMENT: ${{ inputs.react-app-mmi-environment }} REACT_APP_WALLETCONNECT_PROJECT_ID: ${{ inputs.react-app-walletconnect-project-id }} + REACT_APP_RAMP_APIKEY: ${{ inputs.react-app-ramp-api }} - name: Push changes uses: ad-m/github-push-action@8407731efefc0d8f72af254c74276b7a90be36e1 diff --git a/.github/actions/setup-env/action.yml b/.github/actions/setup-env/action.yml index b27d0df44..f983278cf 100644 --- a/.github/actions/setup-env/action.yml +++ b/.github/actions/setup-env/action.yml @@ -6,7 +6,7 @@ inputs: node-version: description: Node.js version required: false - default: '16' + default: 18 aws-secret-access-key: description: AWS secret access key required: true @@ -32,7 +32,9 @@ runs: run: | sudo apt-get update sudo apt-get -y install python3-pip python3-dev - pip install awscli --upgrade --user + python -m venv venv + source venv/bin/activate + pip install awscli --upgrade - name: Project dependencies setup, node version ${{ inputs.node-version }} shell: bash run: yarn install --frozen-lockfile diff --git a/.github/workflows/cla.yml b/.github/workflows/cla.yml index 63db9080d..bb6d845a6 100644 --- a/.github/workflows/cla.yml +++ b/.github/workflows/cla.yml @@ -6,7 +6,7 @@ on: types: [opened, closed, synchronize] jobs: - CLAssistant: + CLAAssistant: runs-on: ubuntu-latest steps: - name: 'CLA Assistant' @@ -19,11 +19,11 @@ jobs: PERSONAL_ACCESS_TOKEN: ${{ secrets.CLA_ACCESS_TOKEN }} with: path-to-signatures: 'signatures/version1/cla.json' - path-to-document: 'https://safe.global/cla/' # e.g. a CLA or a DCO document + path-to-document: 'https://safe.global/cla' # e.g. a CLA or a DCO document # branch should not be protected branch: 'main' # user names of users allowed to contribute without CLA - allowlist: mikhailxyz,rmeissner,germartinez,Uxio0,dasanra,francovenica,luarx,DaniSomoza,yagopv,JagoFigueroa,bot* + allowlist: clovisdasilvaneto,rmeissner,Uxio0,dasanra,francovenica,luarx,yagopv,usame-algan,bot* # the followings are the optional inputs - If the optional inputs are not given, then default values will be taken # enter the remote organization name where the signatures should be stored (Default is storing the signatures in the same repository) diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml index 70244acfb..1b3f35e28 100644 --- a/.github/workflows/deployment.yml +++ b/.github/workflows/deployment.yml @@ -32,7 +32,7 @@ jobs: aws-region: ${{ secrets.AWS_DEFAULT_REGION }} - name: Derive appropriate SHAs for base and head for `nx affected` commands - uses: nrwl/nx-set-shas@v2 + uses: nrwl/nx-set-shas@v4 - name: Test, Build, Deploy PR run: | @@ -48,6 +48,7 @@ jobs: REACT_APP_MMI_BACKEND_BASE_URL: ${{ secrets.MMI_BACKEND_BASE_URL }} REACT_APP_MMI_ENVIRONMENT: ${{ secrets.MMI_ENVIRONMENT }} REACT_APP_WALLETCONNECT_PROJECT_ID: ${{ secrets.REACT_APP_WALLETCONNECT_PROJECT_ID_DEVSTAGING }} + REACT_APP_RAMP_APIKEY: ${{ secrets.REACT_APP_RAMP_APIKEY }} - name: 'PRaul: Comment PR with app URLs' if: success() && github.event.number @@ -57,7 +58,6 @@ jobs: * [Drain Account App](${{ env.REVIEW_FEATURE_URL }}/drain-safe/) * [RAMP Network App](${{ env.REVIEW_FEATURE_URL }}/ramp-network/) * [Sign-In With Ethereum Delegate Manager App](${{ env.REVIEW_FEATURE_URL }}/siwe-delegate-manager/) - * [Safe Claiming App](${{ env.REVIEW_FEATURE_URL }}/safe-claiming-app/) * [Tx Builder App](${{ env.REVIEW_FEATURE_URL }}/tx-builder/) * [WalletConnect app](${{ env.REVIEW_FEATURE_URL }}/wallet-connect/) * [MMI App](${{ env.REVIEW_FEATURE_URL }}/mmi/) @@ -83,7 +83,7 @@ jobs: aws-region: ${{ secrets.AWS_DEFAULT_REGION }} - name: Derive appropriate SHAs for base and head for `nx affected` commands - uses: nrwl/nx-set-shas@v2 + uses: nrwl/nx-set-shas@v4 - name: Test and Build run: | @@ -96,6 +96,7 @@ jobs: REACT_APP_MMI_BACKEND_BASE_URL: ${{ secrets.MMI_BACKEND_BASE_URL }} REACT_APP_MMI_ENVIRONMENT: ${{ secrets.MMI_ENVIRONMENT }} REACT_APP_WALLETCONNECT_PROJECT_ID: ${{ secrets.REACT_APP_WALLETCONNECT_PROJECT_ID_DEVSTAGING }} + REACT_APP_RAMP_APIKEY: ${{ secrets.REACT_APP_RAMP_APIKEY }} # Script to deploy to the dev environment - name: 'Deploy to S3: Develop' @@ -141,3 +142,4 @@ jobs: react-app-mmi-backend-base-url: ${{ secrets.MMI_BACKEND_BASE_URL }} react-app-mmi-environment: ${{ secrets.MMI_ENVIRONMENT }} react-app-walletconnect-project-id: ${{ secrets.REACT_APP_WALLETCONNECT_PROJECT_ID }} + react-app-ramp-api: ${{ secrets.REACT_APP_RAMP_APIKEY }} diff --git a/.github/workflows/safe-apps-check.yml b/.github/workflows/safe-apps-check.yml index d676ff1e3..5dd0f487d 100644 --- a/.github/workflows/safe-apps-check.yml +++ b/.github/workflows/safe-apps-check.yml @@ -22,9 +22,9 @@ on: description: 'Config service base URL' required: true default: 'https://safe-client.safe.global' - schedule: - # At 9:00 on every day-of-week from Monday through Friday - - cron: '0 9 * * 1-5' + # schedule: + # # At 4:00 on every day-of-week from Monday through Friday + # - cron: '0 4 * * 1-5' jobs: e2e: @@ -177,21 +177,3 @@ jobs: SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} continue-on-error: true - - - uses: cypress-io/github-action@v5 - name: Volta - with: - browser: chrome - record: true - spec: cypress/e2e/safe-apps-check.spec.cy.js - env: - CI: 'true' - CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} - CYPRESS_WEB_BASE_URL: ${{ github.event.inputs.baseUrl || 'https://app.safe.global' }} - CYPRESS_CHAIN_ID: ${{ github.event.inputs.chainId || '73799' }} - CYPRESS_NETWORK_PREFIX: ${{ github.event.inputs.networkPrefix || 'vt' }} - CYPRESS_TESTING_SAFE_ADDRESS: ${{ github.event.inputs.safeAddress || '0x6eaD891f8a756edBf248A5546ff51E01609C0DaB' }} - CYPRESS_CLIENT_GATEWAY_BASE_URL: ${{ github.event.inputs.configServiceBaseUrl || 'https://safe-client.safe.global' }} - SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - continue-on-error: true diff --git a/.github/workflows/safe-apps-e2e.yml b/.github/workflows/safe-apps-e2e.yml index ffe467080..82532b32d 100644 --- a/.github/workflows/safe-apps-e2e.yml +++ b/.github/workflows/safe-apps-e2e.yml @@ -52,19 +52,16 @@ jobs: - name: Set PR Safe Apps base URL run: | echo "SAFE_APPS_BASE_URL=https://pr${{ github.event.number }}--safereactapps.review-react-hr.5afe.dev" >> $GITHUB_ENV - echo "BASE_URL=https://safe-web.dev.5afe.dev/app" >> $GITHUB_ENV if: github.ref != 'refs/heads/development' - name: Set development Safe Apps base URL run: | echo "SAFE_APPS_BASE_URL=https://safe-apps.dev.5afe.dev" >> $GITHUB_ENV - echo "BASE_URL=https://safe-web.dev.5afe.dev/app" >> $GITHUB_ENV if: github.ref == 'refs/heads/development' - name: Set main Safe Apps base URL run: | - echo "SAFE_APPS_BASE_URL=https://apps.gnosis-safe.io" >> $GITHUB_ENV - echo "BASE_URL=https://gnosis-safe.io/app" >> $GITHUB_ENV + echo "SAFE_APPS_BASE_URL=https://apps-portal.safe.global" >> $GITHUB_ENV if: ${{ github.ref == 'refs/heads/main' || github.event.schedule == '0 9 * * 1-5' }} - name: Checkout safe-react-apps diff --git a/.nvmrc b/.nvmrc index b6a7d89c6..3c032078a 100644 --- a/.nvmrc +++ b/.nvmrc @@ -1 +1 @@ -16 +18 diff --git a/README.md b/README.md index be6bc098b..1a9d5933f 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # Safe Apps -[![Logo](https://raw.githubusercontent.com/safe-global/safe-react-apps/main/assets/logo.png)](https://safe.global/) +[![Logo](https://raw.githubusercontent.com/safe-global/safe-react-apps/main/assets/logo.svg)](https://safe.global/) ![license](https://img.shields.io/github/license/safe-global/safe-react-apps) ![build](https://img.shields.io/github/actions/workflow/status/safe-global/safe-react-apps/deployment.yml?branch=main) @@ -62,11 +62,11 @@ In `./apps` you can find one folder per each integration app Gnosis develops. Also, each app must expose a `manifest.json` in order to be accepted by the Safe, you can find it in their `./public` folder. Besides the `manifest.json` file we also include the app Icon. -This will allow the Safe to consume these resources like so: `https://apps.gnosis-safe.io/tx-builder/manifest.json` +This will allow the Safe to consume these resources like so: `https://apps-portal.safe.global/tx-builder/manifest.json` ## How to Develop a third-party App -Documentation about how to develop and integrate your third-party app can be found [here](https://docs.gnosis-safe.io/build/sdks/safe-apps). +Documentation about how to develop and integrate your third-party app can be found [here](https://docs.safe.global/safe-core-aa-sdk/safe-apps). ## For developers @@ -98,12 +98,12 @@ These apps are deployed in the following environments. | [Transaction Builder](https://safe-apps.staging.5afe.dev/tx-builder) | [WalletConnect](https://safe-apps.staging.5afe.dev/wallet-connect) -- When the code is released: https://apps.gnosis-safe.io +- When the code is released: https://apps-portal.safe.global - [Drain Account](https://apps.gnosis-safe.io/drain-safe) - | [Ramp Network](https://apps.gnosis-safe.io/ramp-network) - | [Transaction Builder](https://apps.gnosis-safe.io/tx-builder) - | [WalletConnect](https://apps.gnosis-safe.io/wallet-connect) + [Drain Account](https://apps-portal.safe.global/drain-safe) + | [Ramp Network](https://apps-portal.safe.global/ramp-network) + | [Transaction Builder](https://apps-portal.safe.global/tx-builder) + | [WalletConnect](https://apps-portal.safe.global/wallet-connect) ## Run e2e tests diff --git a/apps/drain-safe/CHANGELOG.md b/apps/drain-safe/CHANGELOG.md index 36900d077..0118587f2 100644 --- a/apps/drain-safe/CHANGELOG.md +++ b/apps/drain-safe/CHANGELOG.md @@ -2,6 +2,19 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [1.5.1](https://github.com/safe-global/safe-react-apps/compare/drain-safe-1.5.0...drain-safe-1.5.1) (2023-08-23) + + + +# [1.5.0](https://github.com/safe-global/safe-react-apps/compare/drain-safe-1.4.0...drain-safe-1.5.0) (2023-05-11) + + +### Features + +* **wallet-connect:** update safe-apps-sdk to enable synchronous off-chain signatures ([#657](https://github.com/safe-global/safe-react-apps/issues/657)) ([6b1562f](https://github.com/safe-global/safe-react-apps/commit/6b1562f59eeaff662c212ee0b71f7df602cf0185)) + + + # [1.4.0](https://github.com/safe-global/safe-react-apps/compare/drain-safe-1.3.6...drain-safe-1.4.0) (2023-02-27) diff --git a/apps/drain-safe/package.json b/apps/drain-safe/package.json index 10c5b4f86..e30576722 100644 --- a/apps/drain-safe/package.json +++ b/apps/drain-safe/package.json @@ -1,13 +1,13 @@ { "name": "drain-safe", - "version": "1.4.0", + "version": "1.5.1", "private": true, "dependencies": { "@gnosis.pm/safe-react-components": "^1.2.0", "@material-ui/core": "^4.12.4", "@mui/x-data-grid": "4.0.2", - "@safe-global/safe-apps-provider": "^0.16.0", - "bignumber.js": "^9.1.0", + "@safe-global/safe-apps-provider": "^0.18.0", + "bignumber.js": "^9.1.1", "web3-eth-abi": "~1.8.1" }, "scripts": { diff --git a/apps/drain-safe/src/__tests__/sdk-helpers.test.js b/apps/drain-safe/src/__tests__/sdk-helpers.test.js index 8effc25ee..f8fb42518 100644 --- a/apps/drain-safe/src/__tests__/sdk-helpers.test.js +++ b/apps/drain-safe/src/__tests__/sdk-helpers.test.js @@ -1,6 +1,14 @@ import { encodeTxData, tokenToTx } from '../utils/sdk-helpers' import erc20 from '../abis/erc20' +// Axios is bundled as ESM module which is not directly compatible with Jest +// https://jestjs.io/docs/ecmascript-modules +jest.mock('axios', () => ({ + get: jest.fn(), + post: jest.fn(), + delete: jest.fn(), +})) + describe('Safe SDK helpers', () => { describe('encodeTxData', () => { it('encodes a simple transfer call', () => { diff --git a/apps/drain-safe/src/setupTests.ts b/apps/drain-safe/src/setupTests.ts index 264828a90..f90717111 100644 --- a/apps/drain-safe/src/setupTests.ts +++ b/apps/drain-safe/src/setupTests.ts @@ -1 +1,8 @@ import '@testing-library/jest-dom/extend-expect' + +// Jest is not able to use this function from node, which is used at viem v1.3.0 +// We need to import it manually +import { TextEncoder } from 'util' + +global.TextEncoder = TextEncoder +// END diff --git a/apps/ramp-network/.env.example b/apps/ramp-network/.env.example new file mode 100644 index 000000000..3a45a0541 --- /dev/null +++ b/apps/ramp-network/.env.example @@ -0,0 +1 @@ +REACT_APP_RAMP_APIKEY= \ No newline at end of file diff --git a/apps/ramp-network/CHANGELOG.md b/apps/ramp-network/CHANGELOG.md index 63274158b..590225d60 100644 --- a/apps/ramp-network/CHANGELOG.md +++ b/apps/ramp-network/CHANGELOG.md @@ -2,6 +2,42 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +# [0.3.0](https://github.com/safe-global/safe-react-apps/compare/ramp-network-0.2.0...ramp-network-0.3.0) (2024-08-20) + + +### Features + +* **ramp:** add Optimism and Linea ([#814](https://github.com/safe-global/safe-react-apps/issues/814)) ([3e63b26](https://github.com/safe-global/safe-react-apps/commit/3e63b26ca9eb9844cfc0de942ef88d5c132202da)) + + + +# [0.2.0](https://github.com/safe-global/safe-react-apps/compare/ramp-network-0.1.4...ramp-network-0.2.0) (2024-01-25) + + +### Bug Fixes + +* **ramp-network:** upgrade SDK to 4.0.4 ([#778](https://github.com/safe-global/safe-react-apps/issues/778)) ([87fd5eb](https://github.com/safe-global/safe-react-apps/commit/87fd5eb1c277d87fec8c3b8f5b157f44b2e7ec82)) + + +### Features + +* add support for additional chains on ramp ([#786](https://github.com/safe-global/safe-react-apps/issues/786)) ([da6ed53](https://github.com/safe-global/safe-react-apps/commit/da6ed53acb2e4a961d9735474ca85d259aab38c4)) + + + +## [0.1.5](https://github.com/safe-global/safe-react-apps/compare/ramp-network-0.1.4...ramp-network-0.1.5) (2023-11-29) + + +### Bug Fixes + +* **ramp-network:** upgrade SDK to 4.0.4 ([#778](https://github.com/safe-global/safe-react-apps/issues/778)) ([87fd5eb](https://github.com/safe-global/safe-react-apps/commit/87fd5eb1c277d87fec8c3b8f5b157f44b2e7ec82)) + + + +## [0.1.4](https://github.com/safe-global/safe-react-apps/compare/ramp-network-0.1.3...ramp-network-0.1.4) (2023-11-09) + + + ## [0.1.3](https://github.com/safe-global/safe-react-apps/compare/ramp-network-0.1.2...ramp-network-0.1.3) (2023-02-06) diff --git a/apps/ramp-network/package.json b/apps/ramp-network/package.json index 5893bee6d..3b77334eb 100644 --- a/apps/ramp-network/package.json +++ b/apps/ramp-network/package.json @@ -1,12 +1,12 @@ { "name": "ramp-network", - "version": "0.1.3", + "version": "0.3.0", "private": true, "homepage": "./", "dependencies": { "@gnosis.pm/safe-react-components": "^0.9.7", "@material-ui/core": "^4.12.4", - "@ramp-network/ramp-instant-sdk": "^3.0.0" + "@ramp-network/ramp-instant-sdk": "^4.0.4" }, "scripts": { "start": "react-app-rewired start", diff --git a/apps/ramp-network/src/App.tsx b/apps/ramp-network/src/App.tsx index 9b35719ff..1093c3fdb 100644 --- a/apps/ramp-network/src/App.tsx +++ b/apps/ramp-network/src/App.tsx @@ -3,7 +3,7 @@ import { useSafeAppsSDK } from '@safe-global/safe-apps-react-sdk' import { Title, Loader, Text } from '@gnosis.pm/safe-react-components' import { ChainInfo } from '@safe-global/safe-apps-sdk' import { goBack } from './utils' -import { ASSETS_BY_CHAIN, getRampWidgetUrl, initializeRampWidget } from './ramp' +import { ASSETS_BY_CHAIN, initializeRampWidget } from './ramp' import styled from 'styled-components' const Container = styled.div` @@ -45,7 +45,6 @@ const SafeApp = (): React.ReactElement | null => { useEffect(() => { if (chainInfo && safe && isChainSupported) { initializeRampWidget({ - url: getRampWidgetUrl(chainInfo), address: safe.safeAddress, assets: ASSETS_BY_CHAIN[chainInfo.chainId], onClose: goBack, diff --git a/apps/ramp-network/src/constants.ts b/apps/ramp-network/src/constants.ts new file mode 100644 index 000000000..64396a695 --- /dev/null +++ b/apps/ramp-network/src/constants.ts @@ -0,0 +1 @@ +export const RAMP_API_KEY = process.env.REACT_APP_RAMP_APIKEY diff --git a/apps/ramp-network/src/ramp.ts b/apps/ramp-network/src/ramp.ts index d583a0be3..b62250010 100644 --- a/apps/ramp-network/src/ramp.ts +++ b/apps/ramp-network/src/ramp.ts @@ -1,37 +1,37 @@ -import { ChainInfo } from '@safe-global/safe-apps-sdk' import { RampInstantEvent, RampInstantSDK } from '@ramp-network/ramp-instant-sdk' +import { RAMP_API_KEY } from './constants' -const RINKEBY_STAGING_URL = 'https://ri-widget-staging.firebaseapp.com/' const WIDGET_CLOSE_EVENT = 'WIDGET_CLOSE' const PURCHASE_CREATED_EVENT = 'PURCHASE_CREATED' export const ASSETS_BY_CHAIN: { [key: string]: string } = { - '1': 'ETH_*,ERC20_*', - '4': 'ETH_*,ERC20_*', + '1': 'ETH_*', + '10': 'OPTIMISM_*', '56': 'BSC_*', '137': 'MATIC_*', '100': 'XDAI_*', '43114': 'AVAX_*', -} - -export const getRampWidgetUrl = (chainInfo: ChainInfo) => { - return chainInfo?.chainId === '4' ? RINKEBY_STAGING_URL : '' + '8453': 'BASE_*', + '324': 'ZKSYNCERA_*', + '1101': 'POLYGONZKEVM_*', + '42161': 'ARBITRUM_*', + '42220': 'CELO_*', + '59144': 'LINEA_*', } type RampWidgetInitializer = { - url?: string assets: string address: string onClose?: () => void } -export const initializeRampWidget = ({ url, assets, address, onClose }: RampWidgetInitializer) => { +export const initializeRampWidget = ({ assets, address, onClose }: RampWidgetInitializer) => { return new RampInstantSDK({ - url, hostAppName: 'Ramp Network Safe App', hostLogoUrl: 'https://docs.ramp.network/img/logo-1.svg', swapAsset: assets, userAddress: address, + hostApiKey: RAMP_API_KEY, }) .on('*', (event: RampInstantEvent) => { if (event.type === WIDGET_CLOSE_EVENT) { diff --git a/apps/safe-claiming-app/.eslintignore b/apps/safe-claiming-app/.eslintignore deleted file mode 100644 index 3a8be72e8..000000000 --- a/apps/safe-claiming-app/.eslintignore +++ /dev/null @@ -1,6 +0,0 @@ -contracts -node_modules -src/setupTests.ts -public -.eslintrc.js -craco.config.ts \ No newline at end of file diff --git a/apps/safe-claiming-app/.gitignore b/apps/safe-claiming-app/.gitignore deleted file mode 100644 index c84dde212..000000000 --- a/apps/safe-claiming-app/.gitignore +++ /dev/null @@ -1,32 +0,0 @@ -# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. - -# dependencies -/node_modules -/.pnp -.pnp.js - -# testing -/coverage -coverage.json - -# production -/build - -# misc -.DS_Store -.env -.env.local -.env.development.local -.env.test.local -.env.production.local -.idea - -npm-debug.log* -yarn-debug.log* -yarn-error.log* - -#Hardhat files -cache -artifacts - -/src/types/contracts \ No newline at end of file diff --git a/apps/safe-claiming-app/.prettierrc b/apps/safe-claiming-app/.prettierrc deleted file mode 100644 index cd6ae75f9..000000000 --- a/apps/safe-claiming-app/.prettierrc +++ /dev/null @@ -1,5 +0,0 @@ -{ - "proseWrap": "always", - "semi": false, - "endOfLine": "auto" -} diff --git a/apps/safe-claiming-app/CHANGELOG.md b/apps/safe-claiming-app/CHANGELOG.md deleted file mode 100644 index 3373c61fb..000000000 --- a/apps/safe-claiming-app/CHANGELOG.md +++ /dev/null @@ -1,105 +0,0 @@ -# Changelog - -This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). - -# [0.5.0](https://github.com/safe-global/safe-react-apps/compare/safe-claiming-app-0.4.2...safe-claiming-app-0.5.0) (2023-02-27) - - -### Features - -* **wallet-connect:** EIP-1271 support ([8084cd3](https://github.com/safe-global/safe-react-apps/commit/8084cd36ca19fb740db740d3279774f6e3e1b890)) - - - -## [0.4.2](https://github.com/safe-global/safe-react-apps/compare/safe-claiming-app-0.4.1...safe-claiming-app-0.4.2) (2023-02-06) - - -### Bug Fixes - -* **safe-claiming-app,mmi:** fixes embedding of DM Sans font ([273687f](https://github.com/safe-global/safe-react-apps/commit/273687f4a2f3333c55a0b89b4d5abec16155728f)) - - - -## [0.4.1](https://github.com/safe-global/safe-react-apps/compare/safe-claiming-app-0.4.0...safe-claiming-app-0.4.1) (2022-12-15) - - -### Bug Fixes - -* **safe-claiming-app:** loading state of unredeemed tokens ([#622](https://github.com/safe-global/safe-react-apps/issues/622)) ([ba97a3c](https://github.com/safe-global/safe-react-apps/commit/ba97a3c0cdce1e7a83d9a1f0b7d67e5d0796df74)) - - - -# [0.4.0](https://github.com/safe-global/safe-react-apps/compare/safe-claiming-app-0.3.1...safe-claiming-app-0.4.0) (2022-12-14) - - -### Bug Fixes - -* modify claiming widget copy ([#613](https://github.com/safe-global/safe-react-apps/issues/613)) ([da41a93](https://github.com/safe-global/safe-react-apps/commit/da41a935fa50545e1079701340bc97fdbdfb6ac7)) -* **safe-claiming-app:** correct title color ([#612](https://github.com/safe-global/safe-react-apps/issues/612)) ([c964728](https://github.com/safe-global/safe-react-apps/commit/c964728ae1e8fce0694ff7f0833e42acbb9b59b1)) -* **safe-claiming-app:** no airdrop logo color in darkmode ([#618](https://github.com/safe-global/safe-react-apps/issues/618)) ([6c30e92](https://github.com/safe-global/safe-react-apps/commit/6c30e92502ffad5eb13f05ae437592cfcb25c9cb)) - - -### Features - -* **safe-claiming-app:** Claiming app change formula ([#616](https://github.com/safe-global/safe-react-apps/issues/616)) ([a08e56a](https://github.com/safe-global/safe-react-apps/commit/a08e56adaf5677e48a9bdb6ee6487728c9ce70ef)) -* **safe-claiming-app:** Claiming app widgets ([#586](https://github.com/safe-global/safe-react-apps/issues/586)) ([4cc5205](https://github.com/safe-global/safe-react-apps/commit/4cc5205c1f1290bf6fd561f29bf4820a18014229)), closes [#582](https://github.com/safe-global/safe-react-apps/issues/582) [#588](https://github.com/safe-global/safe-react-apps/issues/588) [#589](https://github.com/safe-global/safe-react-apps/issues/589) [#593](https://github.com/safe-global/safe-react-apps/issues/593) [#602](https://github.com/safe-global/safe-react-apps/issues/602) - - - -## [0.3.1](https://github.com/safe-global/safe-react-apps/compare/safe-claiming-app-0.3.0...safe-claiming-app-0.3.1) (2022-12-02) - - - -## [0.3.1](https://github.com/safe-global/safe-react-apps/compare/safe-claiming-app-0.3.0...safe-claiming-app-0.3.1) (2022-12-01) - - - -# [0.3.0](https://github.com/safe-global/safe-react-apps/compare/safe-claiming-app-0.2.0...safe-claiming-app-0.3.0) (2022-11-18) - - -### Features - -* **siwe-delegate-manager:** Sign-In With Ethereum Delegate Manager Safe App ([#499](https://github.com/safe-global/safe-react-apps/issues/499)) ([34c36c5](https://github.com/safe-global/safe-react-apps/commit/34c36c580300672c6366ad2d534de0a3b1534058)) - - - -# [0.2.0](https://github.com/safe-global/safe-react-apps/compare/safe-claiming-app-0.1.1...safe-claiming-app-0.2.0) (2022-11-03) - - -### Features - -* **safe-claiming-app:** add support for investor vestings ([#562](https://github.com/safe-global/safe-react-apps/issues/562)) ([63899cb](https://github.com/safe-global/safe-react-apps/commit/63899cb032d30db8aa90c8ebc5e4dd7026038246)) - - - -## [0.1.1](https://github.com/safe-global/safe-react-apps/compare/safe-claiming-app-0.1.0...safe-claiming-app-0.1.1) (2022-10-05) - - -### Bug Fixes - -* ammend allocation percentage ([52ae4f2](https://github.com/safe-global/safe-react-apps/commit/52ae4f27e574a34c827fe61eaa59ab683bc7014f)) -* **safe-claiming-app:** amount calculation uses 30 sec buffer ([#523](https://github.com/safe-global/safe-react-apps/issues/523)) ([a15b40b](https://github.com/safe-global/safe-react-apps/commit/a15b40b67ae411df75a3800925764eb4c7fa2b9b)) -* **safe-claiming-app:** validate custom delegation address ([#541](https://github.com/safe-global/safe-react-apps/issues/541)) ([77c36d5](https://github.com/safe-global/safe-react-apps/commit/77c36d51dfbdef0e54c1cdec452c92a7931f9a18)) - - - -# 0.1.0 (2022-09-22) - - -### Bug Fixes - -* (safe-claiming-app): Text adjustments ([#511](https://github.com/safe-global/safe-react-apps/issues/511)) ([1c851b3](https://github.com/safe-global/safe-react-apps/commit/1c851b374224a4884985f4defddc60786a9a56a9)) -* adjust text + accordion color ([cf6fa21](https://github.com/safe-global/safe-react-apps/commit/cf6fa212eb2b83c865341739e2c84368c5f006bf)) -* **claiming-step:** adds helper text about deadline ([#515](https://github.com/safe-global/safe-react-apps/issues/515)) ([6ff48cc](https://github.com/safe-global/safe-react-apps/commit/6ff48cc706ab80c4ba0efaf718b1e7f2019ee464)) -* fix: Implement QA and design feedback for safe-claiming-app ([7431698](https://github.com/safe-global/safe-react-apps/commit/7431698ed22888695a375466dcceeaff9d279dd5)) -* fix: Remove unneccessary import ([de32179](https://github.com/safe-global/safe-react-apps/commit/de32179cd78b33d0d05f2eaec28b19a38d4975b3)) -* fix: wording in tweet box ([e613799](https://github.com/safe-global/safe-react-apps/commit/e6137995bb1fb1f48d11f334b5cbc46fc65db7c3)) -* fix(safe-claiming-app): Update guardian data and image urls ([#510](https://github.com/safe-global/safe-react-apps/issues/510)) ([e22b877](https://github.com/safe-global/safe-react-apps/commit/e22b877ee41a13f3e5ff73fe564de7123da2fefa)) -* fix(safe-claiming-app): Update vesting data url, add governance forum post url" ([4925841](https://github.com/safe-global/safe-react-apps/commit/4925841470769f1d5c6b8dd1bbe110fe94f1e402)) -* **safe-claiming-app:** add block time desync buffer to amount calculation ([#521](https://github.com/safe-global/safe-react-apps/issues/521)) ([0cc253a](https://github.com/safe-global/safe-react-apps/commit/0cc253a270e44aca625be43a62dbea27c9c20697)) -* **safe-claiming-app:** Add missing contract addresses and delegateId ([#512](https://github.com/safe-global/safe-react-apps/issues/512)) ([115d7ee](https://github.com/safe-global/safe-react-apps/commit/115d7eef645fc431e14d0abe262a8ba9ac3dd3de)) - - -### Features - -* **safe-claiming-app:** new safe app to claim SAFE tokens ([#505](https://github.com/safe-global/safe-react-apps/issues/505)) ([ff77695](https://github.com/safe-global/safe-react-apps/commit/ff7769526c8c4271cd667413e0730d68ad351cf8)) diff --git a/apps/safe-claiming-app/abis/Airdrop.json b/apps/safe-claiming-app/abis/Airdrop.json deleted file mode 100644 index 16b0f16d4..000000000 --- a/apps/safe-claiming-app/abis/Airdrop.json +++ /dev/null @@ -1,502 +0,0 @@ -{ - "_format": "hh-sol-artifact-1", - "contractName": "Airdrop", - "sourceName": "safe-token/contracts/Airdrop.sol", - "abi": [ - { - "inputs": [ - { - "internalType": "address", - "name": "_token", - "type": "address" - }, - { - "internalType": "address", - "name": "_manager", - "type": "address" - }, - { - "internalType": "uint64", - "name": "_redeemDeadline", - "type": "uint64" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "AddedVesting", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - } - ], - "name": "CancelledVesting", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - }, - { - "indexed": true, - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "beneficiary", - "type": "address" - } - ], - "name": "ClaimedVesting", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - } - ], - "name": "PausedVesting", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "bytes32", - "name": "id", - "type": "bytes32" - } - ], - "name": "UnpausedVesting", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - }, - { - "internalType": "uint8", - "name": "", - "type": "uint8" - }, - { - "internalType": "bool", - "name": "", - "type": "bool" - }, - { - "internalType": "uint16", - "name": "", - "type": "uint16" - }, - { - "internalType": "uint64", - "name": "", - "type": "uint64" - }, - { - "internalType": "uint128", - "name": "", - "type": "uint128" - } - ], - "name": "addVesting", - "outputs": [], - "stateMutability": "pure", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "vestingId", - "type": "bytes32" - } - ], - "name": "calculateVestedAmount", - "outputs": [ - { - "internalType": "uint128", - "name": "vestedAmount", - "type": "uint128" - }, - { - "internalType": "uint128", - "name": "claimedAmount", - "type": "uint128" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "vestingId", - "type": "bytes32" - } - ], - "name": "cancelVesting", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "beneficiary", - "type": "address" - } - ], - "name": "claimUnusedTokens", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "vestingId", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "beneficiary", - "type": "address" - }, - { - "internalType": "uint128", - "name": "tokensToClaim", - "type": "uint128" - } - ], - "name": "claimVestedTokens", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "vestingId", - "type": "bytes32" - }, - { - "internalType": "address", - "name": "beneficiary", - "type": "address" - }, - { - "internalType": "uint128", - "name": "tokensToClaim", - "type": "uint128" - } - ], - "name": "claimVestedTokensViaModule", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "_root", - "type": "bytes32" - } - ], - "name": "initializeRoot", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "vestingId", - "type": "bytes32" - } - ], - "name": "pauseVesting", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "poolManager", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "uint8", - "name": "curveType", - "type": "uint8" - }, - { - "internalType": "uint16", - "name": "durationWeeks", - "type": "uint16" - }, - { - "internalType": "uint64", - "name": "startDate", - "type": "uint64" - }, - { - "internalType": "uint128", - "name": "amount", - "type": "uint128" - }, - { - "internalType": "bytes32[]", - "name": "proof", - "type": "bytes32[]" - } - ], - "name": "redeem", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "redeemDeadline", - "outputs": [ - { - "internalType": "uint64", - "name": "", - "type": "uint64" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "root", - "outputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "token", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "tokensAvailableForVesting", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalTokensInVesting", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "vestingId", - "type": "bytes32" - } - ], - "name": "unpauseVesting", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "uint8", - "name": "curveType", - "type": "uint8" - }, - { - "internalType": "bool", - "name": "managed", - "type": "bool" - }, - { - "internalType": "uint16", - "name": "durationWeeks", - "type": "uint16" - }, - { - "internalType": "uint64", - "name": "startDate", - "type": "uint64" - }, - { - "internalType": "uint128", - "name": "amount", - "type": "uint128" - } - ], - "name": "vestingHash", - "outputs": [ - { - "internalType": "bytes32", - "name": "vestingId", - "type": "bytes32" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "bytes32", - "name": "", - "type": "bytes32" - } - ], - "name": "vestings", - "outputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - }, - { - "internalType": "uint8", - "name": "curveType", - "type": "uint8" - }, - { - "internalType": "bool", - "name": "managed", - "type": "bool" - }, - { - "internalType": "uint16", - "name": "durationWeeks", - "type": "uint16" - }, - { - "internalType": "uint64", - "name": "startDate", - "type": "uint64" - }, - { - "internalType": "uint128", - "name": "amount", - "type": "uint128" - }, - { - "internalType": "uint128", - "name": "amountClaimed", - "type": "uint128" - }, - { - "internalType": "uint64", - "name": "pausingDate", - "type": "uint64" - }, - { - "internalType": "bool", - "name": "cancelled", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - } - ], - "bytecode": "0x60e06040523480156200001157600080fd5b506040516200487c3803806200487c83398181016040528101906200003791906200014e565b82828173ffffffffffffffffffffffffffffffffffffffff1660808173ffffffffffffffffffffffffffffffffffffffff1660601b815250508073ffffffffffffffffffffffffffffffffffffffff1660a08173ffffffffffffffffffffffffffffffffffffffff1660601b815250505050428167ffffffffffffffff1611620000f8576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620000ef90620001cb565b60405180910390fd5b8067ffffffffffffffff1660c08167ffffffffffffffff1660c01b81525050505050620002c9565b600081519050620001318162000295565b92915050565b6000815190506200014881620002af565b92915050565b6000806000606084860312156200016457600080fd5b6000620001748682870162000120565b9350506020620001878682870162000120565b92505060406200019a8682870162000137565b9150509250925092565b6000620001b3602783620001ed565b9150620001c08262000246565b604082019050919050565b60006020820190508181036000830152620001e681620001a4565b9050919050565b600082825260208201905092915050565b60006200020b8262000212565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b600067ffffffffffffffff82169050919050565b7f52656465656d20646561646c696e652073686f756c6420626520696e2074686560008201527f2066757475726500000000000000000000000000000000000000000000000000602082015250565b620002a081620001fe565b8114620002ac57600080fd5b50565b620002ba8162000232565b8114620002c657600080fd5b50565b60805160601c60a05160601c60c05160c01c6144ee6200038e6000396000818161140a0152818161191e0152611ac90152600081816103b001528181610670015281816107be01528181610ab801528181610c9801528181610fe80152818161142e015281816118900152611d09015260008181610374015281816104820152818161052f015281816106ac01528181610782015281816108910152818161093e01528181610ba601528181610f30015281816119d90152611d3301526144ee6000f3fe608060405234801561001057600080fd5b50600436106101155760003560e01c80638899f311116100a2578063bf6213e411610071578063bf6213e4146102bc578063d7faa145146102d8578063dc4c90d314610309578063ebf0c71714610327578063fc0c546a1461034557610115565b80638899f3111461022e5780638f5b3c3b1461024c5780639fb0c41d14610268578063a27c7a02146102a057610115565b80632bafa73c116100e95780632bafa73c1461018c5780632e0427aa146101a85780633034d534146101c65780635d76df6b146101e25780636cbab0791461021257610115565b806287b83f1461011a578063073804811461013657806307861fe614610154578063166bbd3b14610170575b600080fd5b610134600480360381019061012f9190612ba5565b610363565b005b61013e610ab0565b60405161014b91906138c5565b60405180910390f35b61016e60048036038101906101699190612b7c565b610ab6565b005b61018a60048036038101906101859190612ba5565b610b95565b005b6101a660048036038101906101a19190612b7c565b610c96565b005b6101b0610f2a565b6040516101bd91906138c5565b60405180910390f35b6101e060048036038101906101db9190612b7c565b610fe6565b005b6101fc60048036038101906101f79190612aca565b6112dd565b60405161020991906133fb565b60405180910390f35b61022c60048036038101906102279190612aca565b6113cd565b005b610236611408565b60405161024391906138e0565b60405180910390f35b61026660048036038101906102619190612b7c565b61142c565b005b610282600480360381019061027d9190612b7c565b61178b565b6040516102979998979695949392919061336e565b60405180910390f35b6102ba60048036038101906102b59190612aa1565b61188e565b005b6102d660048036038101906102d19190612c1d565b611ac7565b005b6102f260048036038101906102ed9190612b7c565b611c25565b60405161030092919061389c565b60405180910390f35b610311611d07565b60405161031e9190613255565b60405180910390f35b61032f611d2b565b60405161033c91906133fb565b60405180910390f35b61034d611d31565b60405161035a9190613255565b60405180910390f35b6000610370848484611d55565b90507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663095ea7b37f0000000000000000000000000000000000000000000000000000000000000000836040518363ffffffff1660e01b81526004016103ed92919061331c565b602060405180830381600087803b15801561040757600080fd5b505af115801561041b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061043f9190612b53565b61047e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610475906134dc565b60405180910390fd5b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b81526004016104d99190613255565b60206040518083038186803b1580156104f157600080fd5b505afa158015610505573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105299190612bf4565b905060007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166370a08231866040518263ffffffff1660e01b81526004016105869190613255565b60206040518083038186803b15801561059e57600080fd5b505afa1580156105b2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105d69190612bf4565b905060003086856040516024016105ef93929190613270565b6040516020818303038152906040527f23b872dd000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505090507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663468721a77f000000000000000000000000000000000000000000000000000000000000000060008460006040518563ffffffff1660e01b81526004016106ef94939291906132d0565b602060405180830381600087803b15801561070957600080fd5b505af115801561071d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107419190612b53565b610780576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107779061387c565b60405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663095ea7b37f000000000000000000000000000000000000000000000000000000000000000060006040518363ffffffff1660e01b81526004016107fc9291906132a7565b602060405180830381600087803b15801561081657600080fd5b505af115801561082a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061084e9190612b53565b61088d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610884906137bc565b60405180910390fd5b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b81526004016108e89190613255565b60206040518083038186803b15801561090057600080fd5b505afa158015610914573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109389190612bf4565b905060007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166370a08231896040518263ffffffff1660e01b81526004016109959190613255565b60206040518083038186803b1580156109ad57600080fd5b505afa1580156109c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109e59190612bf4565b9050856fffffffffffffffffffffffffffffffff1685610a059190613b03565b8214610a46576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a3d9061383c565b60405180910390fd5b856fffffffffffffffffffffffffffffffff1684610a64919061396e565b8114610aa5576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a9c906137dc565b60405180910390fd5b505050505050505050565b60005481565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610b44576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b3b9061373c565b60405180910390fd5b6000801b60025414610b8b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b829061365c565b60405180910390fd5b8060028190555050565b6000610ba2848484611d55565b90507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb84836040518363ffffffff1660e01b8152600401610bff92919061331c565b602060405180830381600087803b158015610c1957600080fd5b505af1158015610c2d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c519190612b53565b610c90576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610c87906136dc565b60405180910390fd5b50505050565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610d24576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d1b9061373c565b60405180910390fd5b6000600160008381526020019081526020016000209050600073ffffffffffffffffffffffffffffffffffffffff168160000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415610dcf576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610dc6906135dc565b60405180910390fd5b8060000160159054906101000a900460ff16610e20576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e17906135bc565b60405180910390fd5b60008160020160009054906101000a900467ffffffffffffffff1667ffffffffffffffff1614610e85576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e7c9061385c565b60405180910390fd5b8060000160189054906101000a900467ffffffffffffffff1667ffffffffffffffff16421115610eb55742610ecf565b8060000160189054906101000a900467ffffffffffffffff165b8160020160006101000a81548167ffffffffffffffff021916908367ffffffffffffffff160217905550817f871860f90ed272e98149ef133136694f00424a57465aa85bfc2535dfd4d450b860405160405180910390a25050565b600080547f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b8152600401610f879190613255565b60206040518083038186803b158015610f9f57600080fd5b505afa158015610fb3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fd79190612bf4565b610fe19190613b03565b905090565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611074576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161106b9061373c565b60405180910390fd5b6000600160008381526020019081526020016000209050600073ffffffffffffffffffffffffffffffffffffffff168160000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141561111f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611116906135dc565b60405180910390fd5b60008160020160009054906101000a900467ffffffffffffffff1667ffffffffffffffff161415611185576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161117c9061381c565b60405180910390fd5b8060020160089054906101000a900460ff16156111d7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016111ce906137fc565b60405180910390fd5b60008160020160009054906101000a900467ffffffffffffffff1667ffffffffffffffff1642111561122c578160020160009054906101000a900467ffffffffffffffff16426112279190613b37565b61122f565b60005b9050808260000160189054906101000a900467ffffffffffffffff1661125591906139c4565b8260000160186101000a81548167ffffffffffffffff021916908367ffffffffffffffff16021790555060008260020160006101000a81548167ffffffffffffffff021916908367ffffffffffffffff160217905550827f1bf30dbd59905813e462b38cc5a44f3aadd0ee101ec6d7537139f5c7f8a9868560405160405180910390a2505050565b6000807f47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a7946921860001b463060405160200161131893929190613485565b60405160208183030381529060405280519060200120905060007f43838b5ce9ca440d1ac21b07179a1fdd88aa2175e5ea103f6e37aa6d18ce78ad60001b8989898989896040516020016113729796959493929190613416565b604051602081830303815290604052805190602001209050601960f81b600160f81b83836040516020016113a99493929190613207565b60405160208183030381529060405280519060200120925050509695505050505050565b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016113ff9061355c565b60405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000081565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146114ba576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016114b19061373c565b60405180910390fd5b6000600160008381526020019081526020016000209050600073ffffffffffffffffffffffffffffffffffffffff168160000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415611565576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161155c906135dc565b60405180910390fd5b8060000160159054906101000a900460ff166115b6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016115ad9061369c565b60405180910390fd5b8060020160089054906101000a900460ff1615611608576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016115ff906135fc565b60405180910390fd5b60008160000160189054906101000a900467ffffffffffffffff1667ffffffffffffffff16421115905060008260020160009054906101000a900467ffffffffffffffff1667ffffffffffffffff1614156116ad57806116685742611682565b8160000160189054906101000a900467ffffffffffffffff165b8260020160006101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055505b6000816116ed576116bd8361205a565b8360010160009054906101000a90046fffffffffffffffffffffffffffffffff166116e89190613acf565b61170f565b8260010160009054906101000a90046fffffffffffffffffffffffffffffffff165b9050806fffffffffffffffffffffffffffffffff166000808282546117349190613b03565b9250508190555060018360020160086101000a81548160ff021916908315150217905550837fc27a46a60d5d211f8ef42242a25d5db8357aefb1bd0e7d8fbab00d4c40d4d1c760405160405180910390a250505050565b60016020528060005260406000206000915090508060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16908060000160149054906101000a900460ff16908060000160159054906101000a900460ff16908060000160169054906101000a900461ffff16908060000160189054906101000a900467ffffffffffffffff16908060010160009054906101000a90046fffffffffffffffffffffffffffffffff16908060010160109054906101000a90046fffffffffffffffffffffffffffffffff16908060020160009054906101000a900467ffffffffffffffff16908060020160089054906101000a900460ff16905089565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461191c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016119139061373c565b60405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000067ffffffffffffffff164211611988576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161197f906136bc565b60405180910390fd5b6000611992610f2a565b9050600081116119d7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016119ce9061353c565b60405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb83836040518363ffffffff1660e01b8152600401611a32929190613345565b602060405180830381600087803b158015611a4c57600080fd5b505af1158015611a60573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a849190612b53565b611ac3576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611aba906136dc565b60405180910390fd5b5050565b7f000000000000000000000000000000000000000000000000000000000000000067ffffffffffffffff16421115611b34576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b2b9061351c565b60405180910390fd5b6000801b6002541415611b7c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b739061361c565b60405180910390fd5b6000611b8d338860008989896122c3565b9050611bdd838380806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f8201169050808301925050505050505060025483612763565b611c1c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611c139061377c565b60405180910390fd5b50505050505050565b6000806000600160008581526020019081526020016000209050600073ffffffffffffffffffffffffffffffffffffffff168160000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415611cd3576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611cca906135dc565b60405180910390fd5b611cdc8161205a565b92508060010160109054906101000a90046fffffffffffffffffffffffffffffffff16915050915091565b7f000000000000000000000000000000000000000000000000000000000000000081565b60025481565b7f000000000000000000000000000000000000000000000000000000000000000081565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415611dc6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611dbd906134fc565b60405180910390fd5b60006001600086815260200190815260200160002090503373ffffffffffffffffffffffffffffffffffffffff168160000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614611e6f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611e669061363c565b60405180910390fd5b60008160010160109054906101000a90046fffffffffffffffffffffffffffffffff16611e9b8361205a565b611ea59190613acf565b90506fffffffffffffffffffffffffffffffff8016846fffffffffffffffffffffffffffffffff1614611ed85783611eda565b805b9250806fffffffffffffffffffffffffffffffff16836fffffffffffffffffffffffffffffffff161115611f43576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611f3a906134bc565b60405180910390fd5b826fffffffffffffffffffffffffffffffff16600080828254611f669190613b03565b92505081905550828260010160108282829054906101000a90046fffffffffffffffffffffffffffffffff16611f9c9190613928565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff1602179055508473ffffffffffffffffffffffffffffffffffffffff168260000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16877f31b718389b1eb92df83ab00c1a5112e5bb8a02c7c1c9c02e1e3c15ad33e0532660405160405180910390a450509392505050565b6000428260000160189054906101000a900467ffffffffffffffff1667ffffffffffffffff1611156120c1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016120b89061371c565b60405180910390fd5b6000603c80601860078660000160169054906101000a900461ffff1661ffff166120eb9190613a8d565b6120f59190613a8d565b6120ff9190613a8d565b6121099190613a8d565b90506000808460020160009054906101000a900467ffffffffffffffff1667ffffffffffffffff161161215f578360000160189054906101000a900467ffffffffffffffff164261215a9190613b37565b61219c565b8360000160189054906101000a900467ffffffffffffffff168460020160009054906101000a900467ffffffffffffffff1661219b9190613b37565b5b90508167ffffffffffffffff168167ffffffffffffffff16106121e1578360010160009054906101000a90046fffffffffffffffffffffffffffffffff1692506122bc565b60008460000160149054906101000a900460ff1660ff161415612230576122298460010160009054906101000a90046fffffffffffffffffffffffffffffffff16828461277a565b92506122bb565b60018460000160149054906101000a900460ff1660ff16141561227f576122788460010160009054906101000a90046fffffffffffffffffffffffffffffffff16828461281c565b92506122ba565b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016122b19061357c565b60405180910390fd5b5b5b5050919050565b60008073ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff161415612334576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161232b9061375c565b60405180910390fd5b60028660ff161061237a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612371906136fc565b60405180910390fd5b6123888787878787876112dd565b9050600073ffffffffffffffffffffffffffffffffffffffff166001600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461242f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016124269061367c565b60405180910390fd5b6000612439610f2a565b9050826fffffffffffffffffffffffffffffffff16811015612490576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016124879061379c565b60405180910390fd5b826fffffffffffffffffffffffffffffffff166000808282546124b3919061396e565b925050819055506040518061012001604052808973ffffffffffffffffffffffffffffffffffffffff1681526020018860ff16815260200187151581526020018661ffff1681526020018567ffffffffffffffff168152602001846fffffffffffffffffffffffffffffffff16815260200160006fffffffffffffffffffffffffffffffff168152602001600067ffffffffffffffff168152602001600015158152506001600084815260200190815260200160002060008201518160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060208201518160000160146101000a81548160ff021916908360ff16021790555060408201518160000160156101000a81548160ff02191690831515021790555060608201518160000160166101000a81548161ffff021916908361ffff16021790555060808201518160000160186101000a81548167ffffffffffffffff021916908367ffffffffffffffff16021790555060a08201518160010160006101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff16021790555060c08201518160010160106101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff16021790555060e08201518160020160006101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506101008201518160020160086101000a81548160ff0219169083151502179055509050508773ffffffffffffffffffffffffffffffffffffffff16827fff2781f5af6cf115d187dd0e4ef590f5d1288b83ef3eb6739c69db99e70c8b4d60405160405180910390a3509695505050505050565b60008261277085846128e8565b1490509392505050565b6000808267ffffffffffffffff168467ffffffffffffffff16866fffffffffffffffffffffffffffffffff166127b09190613a33565b6127ba9190613a02565b90506fffffffffffffffffffffffffffffffff8016811115612811576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016128089061359c565b60405180910390fd5b809150509392505050565b6000808267ffffffffffffffff168367ffffffffffffffff1661283f9190613a33565b8467ffffffffffffffff168567ffffffffffffffff16876fffffffffffffffffffffffffffffffff166128729190613a33565b61287c9190613a33565b6128869190613a02565b90506fffffffffffffffffffffffffffffffff80168111156128dd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016128d49061359c565b60405180910390fd5b809150509392505050565b60008082905060005b8451811015612978576000858281518110612935577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60200260200101519050808311612957576129508382612983565b9250612964565b6129618184612983565b92505b50808061297090613cc1565b9150506128f1565b508091505092915050565b600082600052816020526040600020905092915050565b6000813590506129a981614400565b92915050565b60008083601f8401126129c157600080fd5b8235905067ffffffffffffffff8111156129da57600080fd5b6020830191508360208202830111156129f257600080fd5b9250929050565b600081359050612a0881614417565b92915050565b600081519050612a1d81614417565b92915050565b600081359050612a328161442e565b92915050565b600081359050612a4781614445565b92915050565b600081359050612a5c8161445c565b92915050565b600081519050612a7181614473565b92915050565b600081359050612a868161448a565b92915050565b600081359050612a9b816144a1565b92915050565b600060208284031215612ab357600080fd5b6000612ac18482850161299a565b91505092915050565b60008060008060008060c08789031215612ae357600080fd5b6000612af189828a0161299a565b9650506020612b0289828a01612a8c565b9550506040612b1389828a016129f9565b9450506060612b2489828a01612a4d565b9350506080612b3589828a01612a77565b92505060a0612b4689828a01612a38565b9150509295509295509295565b600060208284031215612b6557600080fd5b6000612b7384828501612a0e565b91505092915050565b600060208284031215612b8e57600080fd5b6000612b9c84828501612a23565b91505092915050565b600080600060608486031215612bba57600080fd5b6000612bc886828701612a23565b9350506020612bd98682870161299a565b9250506040612bea86828701612a38565b9150509250925092565b600060208284031215612c0657600080fd5b6000612c1484828501612a62565b91505092915050565b60008060008060008060a08789031215612c3657600080fd5b6000612c4489828a01612a8c565b9650506020612c5589828a01612a4d565b9550506040612c6689828a01612a77565b9450506060612c7789828a01612a38565b935050608087013567ffffffffffffffff811115612c9457600080fd5b612ca089828a016129af565b92509250509295509295509295565b612cb881613b6b565b82525050565b612cc781613b7d565b82525050565b612cde612cd982613b89565b613d0a565b82525050565b612ced81613bb5565b82525050565b612d04612cff82613bb5565b613d14565b82525050565b6000612d15826138fb565b612d1f8185613906565b9350612d2f818560208601613c8e565b612d3881613d7c565b840191505092915050565b612d4c81613c34565b82525050565b612d5b81613c58565b82525050565b612d6a81613c6a565b82525050565b6000612d7d601f83613917565b9150612d8882613d8d565b602082019050919050565b6000612da0601883613917565b9150612dab82613db6565b602082019050919050565b6000612dc3601983613917565b9150612dce82613ddf565b602082019050919050565b6000612de6602c83613917565b9150612df182613e08565b604082019050919050565b6000612e09601283613917565b9150612e1482613e57565b602082019050919050565b6000612e2c602e83613917565b9150612e3782613e80565b604082019050919050565b6000612e4f601283613917565b9150612e5a82613ecf565b602082019050919050565b6000612e72601d83613917565b9150612e7d82613ef8565b602082019050919050565b6000612e95602383613917565b9150612ea082613f21565b604082019050919050565b6000612eb8601183613917565b9150612ec382613f70565b602082019050919050565b6000612edb601983613917565b9150612ee682613f99565b602082019050919050565b6000612efe601a83613917565b9150612f0982613fc2565b602082019050919050565b6000612f21602483613917565b9150612f2c82613feb565b604082019050919050565b6000612f44601e83613917565b9150612f4f8261403a565b602082019050919050565b6000612f67601783613917565b9150612f7282614063565b602082019050919050565b6000612f8a602683613917565b9150612f958261408c565b604082019050919050565b6000612fad601c83613917565b9150612fb8826140db565b602082019050919050565b6000612fd0601583613917565b9150612fdb82614104565b602082019050919050565b6000612ff3601583613917565b9150612ffe8261412d565b602082019050919050565b6000613016601683613917565b915061302182614156565b602082019050919050565b6000613039602283613917565b91506130448261417f565b604082019050919050565b600061305c600f83613917565b9150613067826141ce565b602082019050919050565b600061307f601483613917565b915061308a826141f7565b602082019050919050565b60006130a2601b83613917565b91506130ad82614220565b602082019050919050565b60006130c5602283613917565b91506130d082614249565b604082019050919050565b60006130e8602383613917565b91506130f382614298565b604082019050919050565b600061310b603183613917565b9150613116826142e7565b604082019050919050565b600061312e601583613917565b915061313982614336565b602082019050919050565b6000613151602183613917565b915061315c8261435f565b604082019050919050565b6000613174601683613917565b915061317f826143ae565b602082019050919050565b6000613197601983613917565b91506131a2826143d7565b602082019050919050565b6131b681613bbf565b82525050565b6131c581613c7c565b82525050565b6131d481613bdb565b82525050565b6131e381613c09565b82525050565b6131f281613c13565b82525050565b61320181613c27565b82525050565b60006132138287612ccd565b6001820191506132238286612ccd565b6001820191506132338285612cf3565b6020820191506132438284612cf3565b60208201915081905095945050505050565b600060208201905061326a6000830184612caf565b92915050565b60006060820190506132856000830186612caf565b6132926020830185612caf565b61329f60408301846131ad565b949350505050565b60006040820190506132bc6000830185612caf565b6132c96020830184612d52565b9392505050565b60006080820190506132e56000830187612caf565b6132f26020830186612d52565b81810360408301526133048185612d0a565b90506133136060830184612d61565b95945050505050565b60006040820190506133316000830185612caf565b61333e60208301846131bc565b9392505050565b600060408201905061335a6000830185612caf565b61336760208301846131da565b9392505050565b600061012082019050613384600083018c612caf565b613391602083018b6131f8565b61339e604083018a612cbe565b6133ab60608301896131cb565b6133b860808301886131e9565b6133c560a08301876131ad565b6133d260c08301866131ad565b6133df60e08301856131e9565b6133ed610100830184612cbe565b9a9950505050505050505050565b60006020820190506134106000830184612ce4565b92915050565b600060e08201905061342b600083018a612ce4565b6134386020830189612caf565b61344560408301886131f8565b6134526060830187612cbe565b61345f60808301866131cb565b61346c60a08301856131e9565b61347960c08301846131ad565b98975050505050505050565b600060608201905061349a6000830186612ce4565b6134a760208301856131da565b6134b46040830184612d43565b949350505050565b600060208201905081810360008301526134d581612d70565b9050919050565b600060208201905081810360008301526134f581612d93565b9050919050565b6000602082019050818103600083015261351581612db6565b9050919050565b6000602082019050818103600083015261353581612dd9565b9050919050565b6000602082019050818103600083015261355581612dfc565b9050919050565b6000602082019050818103600083015261357581612e1f565b9050919050565b6000602082019050818103600083015261359581612e42565b9050919050565b600060208201905081810360008301526135b581612e65565b9050919050565b600060208201905081810360008301526135d581612e88565b9050919050565b600060208201905081810360008301526135f581612eab565b9050919050565b6000602082019050818103600083015261361581612ece565b9050919050565b6000602082019050818103600083015261363581612ef1565b9050919050565b6000602082019050818103600083015261365581612f14565b9050919050565b6000602082019050818103600083015261367581612f37565b9050919050565b6000602082019050818103600083015261369581612f5a565b9050919050565b600060208201905081810360008301526136b581612f7d565b9050919050565b600060208201905081810360008301526136d581612fa0565b9050919050565b600060208201905081810360008301526136f581612fc3565b9050919050565b6000602082019050818103600083015261371581612fe6565b9050919050565b6000602082019050818103600083015261373581613009565b9050919050565b600060208201905081810360008301526137558161302c565b9050919050565b600060208201905081810360008301526137758161304f565b9050919050565b6000602082019050818103600083015261379581613072565b9050919050565b600060208201905081810360008301526137b581613095565b9050919050565b600060208201905081810360008301526137d5816130b8565b9050919050565b600060208201905081810360008301526137f5816130db565b9050919050565b60006020820190508181036000830152613815816130fe565b9050919050565b6000602082019050818103600083015261383581613121565b9050919050565b6000602082019050818103600083015261385581613144565b9050919050565b6000602082019050818103600083015261387581613167565b9050919050565b600060208201905081810360008301526138958161318a565b9050919050565b60006040820190506138b160008301856131ad565b6138be60208301846131ad565b9392505050565b60006020820190506138da60008301846131da565b92915050565b60006020820190506138f560008301846131e9565b92915050565b600081519050919050565b600082825260208201905092915050565b600082825260208201905092915050565b600061393382613bbf565b915061393e83613bbf565b9250826fffffffffffffffffffffffffffffffff0382111561396357613962613d1e565b5b828201905092915050565b600061397982613c09565b915061398483613c09565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156139b9576139b8613d1e565b5b828201905092915050565b60006139cf82613c13565b91506139da83613c13565b92508267ffffffffffffffff038211156139f7576139f6613d1e565b5b828201905092915050565b6000613a0d82613c09565b9150613a1883613c09565b925082613a2857613a27613d4d565b5b828204905092915050565b6000613a3e82613c09565b9150613a4983613c09565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615613a8257613a81613d1e565b5b828202905092915050565b6000613a9882613c13565b9150613aa383613c13565b92508167ffffffffffffffff0483118215151615613ac457613ac3613d1e565b5b828202905092915050565b6000613ada82613bbf565b9150613ae583613bbf565b925082821015613af857613af7613d1e565b5b828203905092915050565b6000613b0e82613c09565b9150613b1983613c09565b925082821015613b2c57613b2b613d1e565b5b828203905092915050565b6000613b4282613c13565b9150613b4d83613c13565b925082821015613b6057613b5f613d1e565b5b828203905092915050565b6000613b7682613be9565b9050919050565b60008115159050919050565b60007fff0000000000000000000000000000000000000000000000000000000000000082169050919050565b6000819050919050565b60006fffffffffffffffffffffffffffffffff82169050919050565b600061ffff82169050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600067ffffffffffffffff82169050919050565b600060ff82169050919050565b6000613c3f82613c46565b9050919050565b6000613c5182613be9565b9050919050565b6000613c6382613c09565b9050919050565b6000613c7582613c27565b9050919050565b6000613c8782613bbf565b9050919050565b60005b83811015613cac578082015181840152602081019050613c91565b83811115613cbb576000848401525b50505050565b6000613ccc82613c09565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821415613cff57613cfe613d1e565b5b600182019050919050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b6000601f19601f8301169050919050565b7f547279696e6720746f20636c61696d20746f6f206d616e7920746f6b656e7300600082015250565b7f436f756c64206e6f7420617070726f766520746f6b656e730000000000000000600082015250565b7f43616e6e6f7420636c61696d20746f20302d6164647265737300000000000000600082015250565b7f446561646c696e6520746f2072656465656d2076657374696e6720686173206260008201527f65656e2065786365656465640000000000000000000000000000000000000000602082015250565b7f4e6f20746f6b656e7320746f20636c61696d0000000000000000000000000000600082015250565b7f54686973206d6574686f64206973206e6f7420617661696c61626c6520666f7260008201527f207468697320636f6e7472616374000000000000000000000000000000000000602082015250565b7f496e76616c696420637572766520747970650000000000000000000000000000600082015250565b7f4f766572666c6f7720696e2063757276652063616c63756c6174696f6e000000600082015250565b7f4f6e6c79206d616e616765642076657374696e67732063616e2062652070617560008201527f7365640000000000000000000000000000000000000000000000000000000000602082015250565b7f56657374696e67206e6f7420666f756e64000000000000000000000000000000600082015250565b7f56657374696e6720616c72656164792063616e63656c6c656400000000000000600082015250565b7f537461746520726f6f74206e6f7420696e697469616c697a6564000000000000600082015250565b7f43616e206f6e6c7920626520636c61696d65642062792076657374696e67206f60008201527f776e657200000000000000000000000000000000000000000000000000000000602082015250565b7f537461746520726f6f7420616c726561647920696e697469616c697a65640000600082015250565b7f56657374696e6720696420616c72656164792075736564000000000000000000600082015250565b7f4f6e6c79206d616e616765642076657374696e67732063616e2062652063616e60008201527f63656c6c65640000000000000000000000000000000000000000000000000000602082015250565b7f546f6b656e732063616e207374696c6c2062652072656465656d656400000000600082015250565b7f546f6b656e207472616e73666572206661696c65640000000000000000000000600082015250565b7f496e76616c69642076657374696e672063757276650000000000000000000000600082015250565b7f56657374696e67206e6f74206163746976652079657400000000000000000000600082015250565b7f43616e206f6e6c792062652063616c6c656420627920706f6f6c206d616e616760008201527f6572000000000000000000000000000000000000000000000000000000000000602082015250565b7f496e76616c6964206163636f756e740000000000000000000000000000000000600082015250565b7f496e76616c6964206d65726b6c652070726f6f66000000000000000000000000600082015250565b7f4e6f7420656e6f75676820746f6b656e7320617661696c61626c650000000000600082015250565b7f436f756c64206e6f742073657420746f6b656e20616c6c6f77616e636520746f60008201527f2030000000000000000000000000000000000000000000000000000000000000602082015250565b7f436f756c64206e6f742061646420746f6b656e7320746f2062656e656669636960008201527f6172790000000000000000000000000000000000000000000000000000000000602082015250565b7f56657374696e6720686173206265656e2063616e63656c6c656420616e64206360008201527f616e6e6f7420626520756e706175736564000000000000000000000000000000602082015250565b7f56657374696e67206973206e6f74207061757365640000000000000000000000600082015250565b7f436f756c64206e6f742064656475637420746f6b656e732066726f6d20706f6f60008201527f6c00000000000000000000000000000000000000000000000000000000000000602082015250565b7f56657374696e6720616c72656164792070617573656400000000000000000000600082015250565b7f4d6f64756c65207472616e73616374696f6e206661696c656400000000000000600082015250565b61440981613b6b565b811461441457600080fd5b50565b61442081613b7d565b811461442b57600080fd5b50565b61443781613bb5565b811461444257600080fd5b50565b61444e81613bbf565b811461445957600080fd5b50565b61446581613bdb565b811461447057600080fd5b50565b61447c81613c09565b811461448757600080fd5b50565b61449381613c13565b811461449e57600080fd5b50565b6144aa81613c27565b81146144b557600080fd5b5056fea2646970667358221220a1f112bb520177600c5c66e1af1517724036e52f5bcd813b91f65d7f1b92766764736f6c63430008040033", - "deployedBytecode": "0x608060405234801561001057600080fd5b50600436106101155760003560e01c80638899f311116100a2578063bf6213e411610071578063bf6213e4146102bc578063d7faa145146102d8578063dc4c90d314610309578063ebf0c71714610327578063fc0c546a1461034557610115565b80638899f3111461022e5780638f5b3c3b1461024c5780639fb0c41d14610268578063a27c7a02146102a057610115565b80632bafa73c116100e95780632bafa73c1461018c5780632e0427aa146101a85780633034d534146101c65780635d76df6b146101e25780636cbab0791461021257610115565b806287b83f1461011a578063073804811461013657806307861fe614610154578063166bbd3b14610170575b600080fd5b610134600480360381019061012f9190612ba5565b610363565b005b61013e610ab0565b60405161014b91906138c5565b60405180910390f35b61016e60048036038101906101699190612b7c565b610ab6565b005b61018a60048036038101906101859190612ba5565b610b95565b005b6101a660048036038101906101a19190612b7c565b610c96565b005b6101b0610f2a565b6040516101bd91906138c5565b60405180910390f35b6101e060048036038101906101db9190612b7c565b610fe6565b005b6101fc60048036038101906101f79190612aca565b6112dd565b60405161020991906133fb565b60405180910390f35b61022c60048036038101906102279190612aca565b6113cd565b005b610236611408565b60405161024391906138e0565b60405180910390f35b61026660048036038101906102619190612b7c565b61142c565b005b610282600480360381019061027d9190612b7c565b61178b565b6040516102979998979695949392919061336e565b60405180910390f35b6102ba60048036038101906102b59190612aa1565b61188e565b005b6102d660048036038101906102d19190612c1d565b611ac7565b005b6102f260048036038101906102ed9190612b7c565b611c25565b60405161030092919061389c565b60405180910390f35b610311611d07565b60405161031e9190613255565b60405180910390f35b61032f611d2b565b60405161033c91906133fb565b60405180910390f35b61034d611d31565b60405161035a9190613255565b60405180910390f35b6000610370848484611d55565b90507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663095ea7b37f0000000000000000000000000000000000000000000000000000000000000000836040518363ffffffff1660e01b81526004016103ed92919061331c565b602060405180830381600087803b15801561040757600080fd5b505af115801561041b573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061043f9190612b53565b61047e576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610475906134dc565b60405180910390fd5b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b81526004016104d99190613255565b60206040518083038186803b1580156104f157600080fd5b505afa158015610505573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105299190612bf4565b905060007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166370a08231866040518263ffffffff1660e01b81526004016105869190613255565b60206040518083038186803b15801561059e57600080fd5b505afa1580156105b2573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906105d69190612bf4565b905060003086856040516024016105ef93929190613270565b6040516020818303038152906040527f23b872dd000000000000000000000000000000000000000000000000000000007bffffffffffffffffffffffffffffffffffffffffffffffffffffffff19166020820180517bffffffffffffffffffffffffffffffffffffffffffffffffffffffff838183161783525050505090507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663468721a77f000000000000000000000000000000000000000000000000000000000000000060008460006040518563ffffffff1660e01b81526004016106ef94939291906132d0565b602060405180830381600087803b15801561070957600080fd5b505af115801561071d573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107419190612b53565b610780576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107779061387c565b60405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663095ea7b37f000000000000000000000000000000000000000000000000000000000000000060006040518363ffffffff1660e01b81526004016107fc9291906132a7565b602060405180830381600087803b15801561081657600080fd5b505af115801561082a573d6000803e3d6000fd5b505050506040513d601f19601f8201168201806040525081019061084e9190612b53565b61088d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610884906137bc565b60405180910390fd5b60007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b81526004016108e89190613255565b60206040518083038186803b15801561090057600080fd5b505afa158015610914573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109389190612bf4565b905060007f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166370a08231896040518263ffffffff1660e01b81526004016109959190613255565b60206040518083038186803b1580156109ad57600080fd5b505afa1580156109c1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906109e59190612bf4565b9050856fffffffffffffffffffffffffffffffff1685610a059190613b03565b8214610a46576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a3d9061383c565b60405180910390fd5b856fffffffffffffffffffffffffffffffff1684610a64919061396e565b8114610aa5576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a9c906137dc565b60405180910390fd5b505050505050505050565b60005481565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610b44576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b3b9061373c565b60405180910390fd5b6000801b60025414610b8b576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610b829061365c565b60405180910390fd5b8060028190555050565b6000610ba2848484611d55565b90507f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb84836040518363ffffffff1660e01b8152600401610bff92919061331c565b602060405180830381600087803b158015610c1957600080fd5b505af1158015610c2d573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610c519190612b53565b610c90576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610c87906136dc565b60405180910390fd5b50505050565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614610d24576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d1b9061373c565b60405180910390fd5b6000600160008381526020019081526020016000209050600073ffffffffffffffffffffffffffffffffffffffff168160000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415610dcf576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610dc6906135dc565b60405180910390fd5b8060000160159054906101000a900460ff16610e20576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e17906135bc565b60405180910390fd5b60008160020160009054906101000a900467ffffffffffffffff1667ffffffffffffffff1614610e85576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e7c9061385c565b60405180910390fd5b8060000160189054906101000a900467ffffffffffffffff1667ffffffffffffffff16421115610eb55742610ecf565b8060000160189054906101000a900467ffffffffffffffff165b8160020160006101000a81548167ffffffffffffffff021916908367ffffffffffffffff160217905550817f871860f90ed272e98149ef133136694f00424a57465aa85bfc2535dfd4d450b860405160405180910390a25050565b600080547f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b8152600401610f879190613255565b60206040518083038186803b158015610f9f57600080fd5b505afa158015610fb3573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190610fd79190612bf4565b610fe19190613b03565b905090565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff1614611074576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161106b9061373c565b60405180910390fd5b6000600160008381526020019081526020016000209050600073ffffffffffffffffffffffffffffffffffffffff168160000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16141561111f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611116906135dc565b60405180910390fd5b60008160020160009054906101000a900467ffffffffffffffff1667ffffffffffffffff161415611185576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161117c9061381c565b60405180910390fd5b8060020160089054906101000a900460ff16156111d7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016111ce906137fc565b60405180910390fd5b60008160020160009054906101000a900467ffffffffffffffff1667ffffffffffffffff1642111561122c578160020160009054906101000a900467ffffffffffffffff16426112279190613b37565b61122f565b60005b9050808260000160189054906101000a900467ffffffffffffffff1661125591906139c4565b8260000160186101000a81548167ffffffffffffffff021916908367ffffffffffffffff16021790555060008260020160006101000a81548167ffffffffffffffff021916908367ffffffffffffffff160217905550827f1bf30dbd59905813e462b38cc5a44f3aadd0ee101ec6d7537139f5c7f8a9868560405160405180910390a2505050565b6000807f47e79534a245952e8b16893a336b85a3d9ea9fa8c573f3d803afb92a7946921860001b463060405160200161131893929190613485565b60405160208183030381529060405280519060200120905060007f43838b5ce9ca440d1ac21b07179a1fdd88aa2175e5ea103f6e37aa6d18ce78ad60001b8989898989896040516020016113729796959493929190613416565b604051602081830303815290604052805190602001209050601960f81b600160f81b83836040516020016113a99493929190613207565b60405160208183030381529060405280519060200120925050509695505050505050565b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016113ff9061355c565b60405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000081565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff16146114ba576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016114b19061373c565b60405180910390fd5b6000600160008381526020019081526020016000209050600073ffffffffffffffffffffffffffffffffffffffff168160000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415611565576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161155c906135dc565b60405180910390fd5b8060000160159054906101000a900460ff166115b6576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016115ad9061369c565b60405180910390fd5b8060020160089054906101000a900460ff1615611608576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016115ff906135fc565b60405180910390fd5b60008160000160189054906101000a900467ffffffffffffffff1667ffffffffffffffff16421115905060008260020160009054906101000a900467ffffffffffffffff1667ffffffffffffffff1614156116ad57806116685742611682565b8160000160189054906101000a900467ffffffffffffffff165b8260020160006101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055505b6000816116ed576116bd8361205a565b8360010160009054906101000a90046fffffffffffffffffffffffffffffffff166116e89190613acf565b61170f565b8260010160009054906101000a90046fffffffffffffffffffffffffffffffff165b9050806fffffffffffffffffffffffffffffffff166000808282546117349190613b03565b9250508190555060018360020160086101000a81548160ff021916908315150217905550837fc27a46a60d5d211f8ef42242a25d5db8357aefb1bd0e7d8fbab00d4c40d4d1c760405160405180910390a250505050565b60016020528060005260406000206000915090508060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff16908060000160149054906101000a900460ff16908060000160159054906101000a900460ff16908060000160169054906101000a900461ffff16908060000160189054906101000a900467ffffffffffffffff16908060010160009054906101000a90046fffffffffffffffffffffffffffffffff16908060010160109054906101000a90046fffffffffffffffffffffffffffffffff16908060020160009054906101000a900467ffffffffffffffff16908060020160089054906101000a900460ff16905089565b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff163373ffffffffffffffffffffffffffffffffffffffff161461191c576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016119139061373c565b60405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000067ffffffffffffffff164211611988576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161197f906136bc565b60405180910390fd5b6000611992610f2a565b9050600081116119d7576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016119ce9061353c565b60405180910390fd5b7f000000000000000000000000000000000000000000000000000000000000000073ffffffffffffffffffffffffffffffffffffffff1663a9059cbb83836040518363ffffffff1660e01b8152600401611a32929190613345565b602060405180830381600087803b158015611a4c57600080fd5b505af1158015611a60573d6000803e3d6000fd5b505050506040513d601f19601f82011682018060405250810190611a849190612b53565b611ac3576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611aba906136dc565b60405180910390fd5b5050565b7f000000000000000000000000000000000000000000000000000000000000000067ffffffffffffffff16421115611b34576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b2b9061351c565b60405180910390fd5b6000801b6002541415611b7c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611b739061361c565b60405180910390fd5b6000611b8d338860008989896122c3565b9050611bdd838380806020026020016040519081016040528093929190818152602001838360200280828437600081840152601f19601f8201169050808301925050505050505060025483612763565b611c1c576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611c139061377c565b60405180910390fd5b50505050505050565b6000806000600160008581526020019081526020016000209050600073ffffffffffffffffffffffffffffffffffffffff168160000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161415611cd3576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611cca906135dc565b60405180910390fd5b611cdc8161205a565b92508060010160109054906101000a90046fffffffffffffffffffffffffffffffff16915050915091565b7f000000000000000000000000000000000000000000000000000000000000000081565b60025481565b7f000000000000000000000000000000000000000000000000000000000000000081565b60008073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415611dc6576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611dbd906134fc565b60405180910390fd5b60006001600086815260200190815260200160002090503373ffffffffffffffffffffffffffffffffffffffff168160000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff1614611e6f576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611e669061363c565b60405180910390fd5b60008160010160109054906101000a90046fffffffffffffffffffffffffffffffff16611e9b8361205a565b611ea59190613acf565b90506fffffffffffffffffffffffffffffffff8016846fffffffffffffffffffffffffffffffff1614611ed85783611eda565b805b9250806fffffffffffffffffffffffffffffffff16836fffffffffffffffffffffffffffffffff161115611f43576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611f3a906134bc565b60405180910390fd5b826fffffffffffffffffffffffffffffffff16600080828254611f669190613b03565b92505081905550828260010160108282829054906101000a90046fffffffffffffffffffffffffffffffff16611f9c9190613928565b92506101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff1602179055508473ffffffffffffffffffffffffffffffffffffffff168260000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16877f31b718389b1eb92df83ab00c1a5112e5bb8a02c7c1c9c02e1e3c15ad33e0532660405160405180910390a450509392505050565b6000428260000160189054906101000a900467ffffffffffffffff1667ffffffffffffffff1611156120c1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016120b89061371c565b60405180910390fd5b6000603c80601860078660000160169054906101000a900461ffff1661ffff166120eb9190613a8d565b6120f59190613a8d565b6120ff9190613a8d565b6121099190613a8d565b90506000808460020160009054906101000a900467ffffffffffffffff1667ffffffffffffffff161161215f578360000160189054906101000a900467ffffffffffffffff164261215a9190613b37565b61219c565b8360000160189054906101000a900467ffffffffffffffff168460020160009054906101000a900467ffffffffffffffff1661219b9190613b37565b5b90508167ffffffffffffffff168167ffffffffffffffff16106121e1578360010160009054906101000a90046fffffffffffffffffffffffffffffffff1692506122bc565b60008460000160149054906101000a900460ff1660ff161415612230576122298460010160009054906101000a90046fffffffffffffffffffffffffffffffff16828461277a565b92506122bb565b60018460000160149054906101000a900460ff1660ff16141561227f576122788460010160009054906101000a90046fffffffffffffffffffffffffffffffff16828461281c565b92506122ba565b6040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016122b19061357c565b60405180910390fd5b5b5b5050919050565b60008073ffffffffffffffffffffffffffffffffffffffff168773ffffffffffffffffffffffffffffffffffffffff161415612334576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161232b9061375c565b60405180910390fd5b60028660ff161061237a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401612371906136fc565b60405180910390fd5b6123888787878787876112dd565b9050600073ffffffffffffffffffffffffffffffffffffffff166001600083815260200190815260200160002060000160009054906101000a900473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff161461242f576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016124269061367c565b60405180910390fd5b6000612439610f2a565b9050826fffffffffffffffffffffffffffffffff16811015612490576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016124879061379c565b60405180910390fd5b826fffffffffffffffffffffffffffffffff166000808282546124b3919061396e565b925050819055506040518061012001604052808973ffffffffffffffffffffffffffffffffffffffff1681526020018860ff16815260200187151581526020018661ffff1681526020018567ffffffffffffffff168152602001846fffffffffffffffffffffffffffffffff16815260200160006fffffffffffffffffffffffffffffffff168152602001600067ffffffffffffffff168152602001600015158152506001600084815260200190815260200160002060008201518160000160006101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff16021790555060208201518160000160146101000a81548160ff021916908360ff16021790555060408201518160000160156101000a81548160ff02191690831515021790555060608201518160000160166101000a81548161ffff021916908361ffff16021790555060808201518160000160186101000a81548167ffffffffffffffff021916908367ffffffffffffffff16021790555060a08201518160010160006101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff16021790555060c08201518160010160106101000a8154816fffffffffffffffffffffffffffffffff02191690836fffffffffffffffffffffffffffffffff16021790555060e08201518160020160006101000a81548167ffffffffffffffff021916908367ffffffffffffffff1602179055506101008201518160020160086101000a81548160ff0219169083151502179055509050508773ffffffffffffffffffffffffffffffffffffffff16827fff2781f5af6cf115d187dd0e4ef590f5d1288b83ef3eb6739c69db99e70c8b4d60405160405180910390a3509695505050505050565b60008261277085846128e8565b1490509392505050565b6000808267ffffffffffffffff168467ffffffffffffffff16866fffffffffffffffffffffffffffffffff166127b09190613a33565b6127ba9190613a02565b90506fffffffffffffffffffffffffffffffff8016811115612811576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016128089061359c565b60405180910390fd5b809150509392505050565b6000808267ffffffffffffffff168367ffffffffffffffff1661283f9190613a33565b8467ffffffffffffffff168567ffffffffffffffff16876fffffffffffffffffffffffffffffffff166128729190613a33565b61287c9190613a33565b6128869190613a02565b90506fffffffffffffffffffffffffffffffff80168111156128dd576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016128d49061359c565b60405180910390fd5b809150509392505050565b60008082905060005b8451811015612978576000858281518110612935577f4e487b7100000000000000000000000000000000000000000000000000000000600052603260045260246000fd5b60200260200101519050808311612957576129508382612983565b9250612964565b6129618184612983565b92505b50808061297090613cc1565b9150506128f1565b508091505092915050565b600082600052816020526040600020905092915050565b6000813590506129a981614400565b92915050565b60008083601f8401126129c157600080fd5b8235905067ffffffffffffffff8111156129da57600080fd5b6020830191508360208202830111156129f257600080fd5b9250929050565b600081359050612a0881614417565b92915050565b600081519050612a1d81614417565b92915050565b600081359050612a328161442e565b92915050565b600081359050612a4781614445565b92915050565b600081359050612a5c8161445c565b92915050565b600081519050612a7181614473565b92915050565b600081359050612a868161448a565b92915050565b600081359050612a9b816144a1565b92915050565b600060208284031215612ab357600080fd5b6000612ac18482850161299a565b91505092915050565b60008060008060008060c08789031215612ae357600080fd5b6000612af189828a0161299a565b9650506020612b0289828a01612a8c565b9550506040612b1389828a016129f9565b9450506060612b2489828a01612a4d565b9350506080612b3589828a01612a77565b92505060a0612b4689828a01612a38565b9150509295509295509295565b600060208284031215612b6557600080fd5b6000612b7384828501612a0e565b91505092915050565b600060208284031215612b8e57600080fd5b6000612b9c84828501612a23565b91505092915050565b600080600060608486031215612bba57600080fd5b6000612bc886828701612a23565b9350506020612bd98682870161299a565b9250506040612bea86828701612a38565b9150509250925092565b600060208284031215612c0657600080fd5b6000612c1484828501612a62565b91505092915050565b60008060008060008060a08789031215612c3657600080fd5b6000612c4489828a01612a8c565b9650506020612c5589828a01612a4d565b9550506040612c6689828a01612a77565b9450506060612c7789828a01612a38565b935050608087013567ffffffffffffffff811115612c9457600080fd5b612ca089828a016129af565b92509250509295509295509295565b612cb881613b6b565b82525050565b612cc781613b7d565b82525050565b612cde612cd982613b89565b613d0a565b82525050565b612ced81613bb5565b82525050565b612d04612cff82613bb5565b613d14565b82525050565b6000612d15826138fb565b612d1f8185613906565b9350612d2f818560208601613c8e565b612d3881613d7c565b840191505092915050565b612d4c81613c34565b82525050565b612d5b81613c58565b82525050565b612d6a81613c6a565b82525050565b6000612d7d601f83613917565b9150612d8882613d8d565b602082019050919050565b6000612da0601883613917565b9150612dab82613db6565b602082019050919050565b6000612dc3601983613917565b9150612dce82613ddf565b602082019050919050565b6000612de6602c83613917565b9150612df182613e08565b604082019050919050565b6000612e09601283613917565b9150612e1482613e57565b602082019050919050565b6000612e2c602e83613917565b9150612e3782613e80565b604082019050919050565b6000612e4f601283613917565b9150612e5a82613ecf565b602082019050919050565b6000612e72601d83613917565b9150612e7d82613ef8565b602082019050919050565b6000612e95602383613917565b9150612ea082613f21565b604082019050919050565b6000612eb8601183613917565b9150612ec382613f70565b602082019050919050565b6000612edb601983613917565b9150612ee682613f99565b602082019050919050565b6000612efe601a83613917565b9150612f0982613fc2565b602082019050919050565b6000612f21602483613917565b9150612f2c82613feb565b604082019050919050565b6000612f44601e83613917565b9150612f4f8261403a565b602082019050919050565b6000612f67601783613917565b9150612f7282614063565b602082019050919050565b6000612f8a602683613917565b9150612f958261408c565b604082019050919050565b6000612fad601c83613917565b9150612fb8826140db565b602082019050919050565b6000612fd0601583613917565b9150612fdb82614104565b602082019050919050565b6000612ff3601583613917565b9150612ffe8261412d565b602082019050919050565b6000613016601683613917565b915061302182614156565b602082019050919050565b6000613039602283613917565b91506130448261417f565b604082019050919050565b600061305c600f83613917565b9150613067826141ce565b602082019050919050565b600061307f601483613917565b915061308a826141f7565b602082019050919050565b60006130a2601b83613917565b91506130ad82614220565b602082019050919050565b60006130c5602283613917565b91506130d082614249565b604082019050919050565b60006130e8602383613917565b91506130f382614298565b604082019050919050565b600061310b603183613917565b9150613116826142e7565b604082019050919050565b600061312e601583613917565b915061313982614336565b602082019050919050565b6000613151602183613917565b915061315c8261435f565b604082019050919050565b6000613174601683613917565b915061317f826143ae565b602082019050919050565b6000613197601983613917565b91506131a2826143d7565b602082019050919050565b6131b681613bbf565b82525050565b6131c581613c7c565b82525050565b6131d481613bdb565b82525050565b6131e381613c09565b82525050565b6131f281613c13565b82525050565b61320181613c27565b82525050565b60006132138287612ccd565b6001820191506132238286612ccd565b6001820191506132338285612cf3565b6020820191506132438284612cf3565b60208201915081905095945050505050565b600060208201905061326a6000830184612caf565b92915050565b60006060820190506132856000830186612caf565b6132926020830185612caf565b61329f60408301846131ad565b949350505050565b60006040820190506132bc6000830185612caf565b6132c96020830184612d52565b9392505050565b60006080820190506132e56000830187612caf565b6132f26020830186612d52565b81810360408301526133048185612d0a565b90506133136060830184612d61565b95945050505050565b60006040820190506133316000830185612caf565b61333e60208301846131bc565b9392505050565b600060408201905061335a6000830185612caf565b61336760208301846131da565b9392505050565b600061012082019050613384600083018c612caf565b613391602083018b6131f8565b61339e604083018a612cbe565b6133ab60608301896131cb565b6133b860808301886131e9565b6133c560a08301876131ad565b6133d260c08301866131ad565b6133df60e08301856131e9565b6133ed610100830184612cbe565b9a9950505050505050505050565b60006020820190506134106000830184612ce4565b92915050565b600060e08201905061342b600083018a612ce4565b6134386020830189612caf565b61344560408301886131f8565b6134526060830187612cbe565b61345f60808301866131cb565b61346c60a08301856131e9565b61347960c08301846131ad565b98975050505050505050565b600060608201905061349a6000830186612ce4565b6134a760208301856131da565b6134b46040830184612d43565b949350505050565b600060208201905081810360008301526134d581612d70565b9050919050565b600060208201905081810360008301526134f581612d93565b9050919050565b6000602082019050818103600083015261351581612db6565b9050919050565b6000602082019050818103600083015261353581612dd9565b9050919050565b6000602082019050818103600083015261355581612dfc565b9050919050565b6000602082019050818103600083015261357581612e1f565b9050919050565b6000602082019050818103600083015261359581612e42565b9050919050565b600060208201905081810360008301526135b581612e65565b9050919050565b600060208201905081810360008301526135d581612e88565b9050919050565b600060208201905081810360008301526135f581612eab565b9050919050565b6000602082019050818103600083015261361581612ece565b9050919050565b6000602082019050818103600083015261363581612ef1565b9050919050565b6000602082019050818103600083015261365581612f14565b9050919050565b6000602082019050818103600083015261367581612f37565b9050919050565b6000602082019050818103600083015261369581612f5a565b9050919050565b600060208201905081810360008301526136b581612f7d565b9050919050565b600060208201905081810360008301526136d581612fa0565b9050919050565b600060208201905081810360008301526136f581612fc3565b9050919050565b6000602082019050818103600083015261371581612fe6565b9050919050565b6000602082019050818103600083015261373581613009565b9050919050565b600060208201905081810360008301526137558161302c565b9050919050565b600060208201905081810360008301526137758161304f565b9050919050565b6000602082019050818103600083015261379581613072565b9050919050565b600060208201905081810360008301526137b581613095565b9050919050565b600060208201905081810360008301526137d5816130b8565b9050919050565b600060208201905081810360008301526137f5816130db565b9050919050565b60006020820190508181036000830152613815816130fe565b9050919050565b6000602082019050818103600083015261383581613121565b9050919050565b6000602082019050818103600083015261385581613144565b9050919050565b6000602082019050818103600083015261387581613167565b9050919050565b600060208201905081810360008301526138958161318a565b9050919050565b60006040820190506138b160008301856131ad565b6138be60208301846131ad565b9392505050565b60006020820190506138da60008301846131da565b92915050565b60006020820190506138f560008301846131e9565b92915050565b600081519050919050565b600082825260208201905092915050565b600082825260208201905092915050565b600061393382613bbf565b915061393e83613bbf565b9250826fffffffffffffffffffffffffffffffff0382111561396357613962613d1e565b5b828201905092915050565b600061397982613c09565b915061398483613c09565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff038211156139b9576139b8613d1e565b5b828201905092915050565b60006139cf82613c13565b91506139da83613c13565b92508267ffffffffffffffff038211156139f7576139f6613d1e565b5b828201905092915050565b6000613a0d82613c09565b9150613a1883613c09565b925082613a2857613a27613d4d565b5b828204905092915050565b6000613a3e82613c09565b9150613a4983613c09565b9250817fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff0483118215151615613a8257613a81613d1e565b5b828202905092915050565b6000613a9882613c13565b9150613aa383613c13565b92508167ffffffffffffffff0483118215151615613ac457613ac3613d1e565b5b828202905092915050565b6000613ada82613bbf565b9150613ae583613bbf565b925082821015613af857613af7613d1e565b5b828203905092915050565b6000613b0e82613c09565b9150613b1983613c09565b925082821015613b2c57613b2b613d1e565b5b828203905092915050565b6000613b4282613c13565b9150613b4d83613c13565b925082821015613b6057613b5f613d1e565b5b828203905092915050565b6000613b7682613be9565b9050919050565b60008115159050919050565b60007fff0000000000000000000000000000000000000000000000000000000000000082169050919050565b6000819050919050565b60006fffffffffffffffffffffffffffffffff82169050919050565b600061ffff82169050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600067ffffffffffffffff82169050919050565b600060ff82169050919050565b6000613c3f82613c46565b9050919050565b6000613c5182613be9565b9050919050565b6000613c6382613c09565b9050919050565b6000613c7582613c27565b9050919050565b6000613c8782613bbf565b9050919050565b60005b83811015613cac578082015181840152602081019050613c91565b83811115613cbb576000848401525b50505050565b6000613ccc82613c09565b91507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff821415613cff57613cfe613d1e565b5b600182019050919050565b6000819050919050565b6000819050919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601260045260246000fd5b6000601f19601f8301169050919050565b7f547279696e6720746f20636c61696d20746f6f206d616e7920746f6b656e7300600082015250565b7f436f756c64206e6f7420617070726f766520746f6b656e730000000000000000600082015250565b7f43616e6e6f7420636c61696d20746f20302d6164647265737300000000000000600082015250565b7f446561646c696e6520746f2072656465656d2076657374696e6720686173206260008201527f65656e2065786365656465640000000000000000000000000000000000000000602082015250565b7f4e6f20746f6b656e7320746f20636c61696d0000000000000000000000000000600082015250565b7f54686973206d6574686f64206973206e6f7420617661696c61626c6520666f7260008201527f207468697320636f6e7472616374000000000000000000000000000000000000602082015250565b7f496e76616c696420637572766520747970650000000000000000000000000000600082015250565b7f4f766572666c6f7720696e2063757276652063616c63756c6174696f6e000000600082015250565b7f4f6e6c79206d616e616765642076657374696e67732063616e2062652070617560008201527f7365640000000000000000000000000000000000000000000000000000000000602082015250565b7f56657374696e67206e6f7420666f756e64000000000000000000000000000000600082015250565b7f56657374696e6720616c72656164792063616e63656c6c656400000000000000600082015250565b7f537461746520726f6f74206e6f7420696e697469616c697a6564000000000000600082015250565b7f43616e206f6e6c7920626520636c61696d65642062792076657374696e67206f60008201527f776e657200000000000000000000000000000000000000000000000000000000602082015250565b7f537461746520726f6f7420616c726561647920696e697469616c697a65640000600082015250565b7f56657374696e6720696420616c72656164792075736564000000000000000000600082015250565b7f4f6e6c79206d616e616765642076657374696e67732063616e2062652063616e60008201527f63656c6c65640000000000000000000000000000000000000000000000000000602082015250565b7f546f6b656e732063616e207374696c6c2062652072656465656d656400000000600082015250565b7f546f6b656e207472616e73666572206661696c65640000000000000000000000600082015250565b7f496e76616c69642076657374696e672063757276650000000000000000000000600082015250565b7f56657374696e67206e6f74206163746976652079657400000000000000000000600082015250565b7f43616e206f6e6c792062652063616c6c656420627920706f6f6c206d616e616760008201527f6572000000000000000000000000000000000000000000000000000000000000602082015250565b7f496e76616c6964206163636f756e740000000000000000000000000000000000600082015250565b7f496e76616c6964206d65726b6c652070726f6f66000000000000000000000000600082015250565b7f4e6f7420656e6f75676820746f6b656e7320617661696c61626c650000000000600082015250565b7f436f756c64206e6f742073657420746f6b656e20616c6c6f77616e636520746f60008201527f2030000000000000000000000000000000000000000000000000000000000000602082015250565b7f436f756c64206e6f742061646420746f6b656e7320746f2062656e656669636960008201527f6172790000000000000000000000000000000000000000000000000000000000602082015250565b7f56657374696e6720686173206265656e2063616e63656c6c656420616e64206360008201527f616e6e6f7420626520756e706175736564000000000000000000000000000000602082015250565b7f56657374696e67206973206e6f74207061757365640000000000000000000000600082015250565b7f436f756c64206e6f742064656475637420746f6b656e732066726f6d20706f6f60008201527f6c00000000000000000000000000000000000000000000000000000000000000602082015250565b7f56657374696e6720616c72656164792070617573656400000000000000000000600082015250565b7f4d6f64756c65207472616e73616374696f6e206661696c656400000000000000600082015250565b61440981613b6b565b811461441457600080fd5b50565b61442081613b7d565b811461442b57600080fd5b50565b61443781613bb5565b811461444257600080fd5b50565b61444e81613bbf565b811461445957600080fd5b50565b61446581613bdb565b811461447057600080fd5b50565b61447c81613c09565b811461448757600080fd5b50565b61449381613c13565b811461449e57600080fd5b50565b6144aa81613c27565b81146144b557600080fd5b5056fea2646970667358221220a1f112bb520177600c5c66e1af1517724036e52f5bcd813b91f65d7f1b92766764736f6c63430008040033", - "linkReferences": {}, - "deployedLinkReferences": {} -} diff --git a/apps/safe-claiming-app/abis/SafeToken.json b/apps/safe-claiming-app/abis/SafeToken.json deleted file mode 100644 index 683a2c8a8..000000000 --- a/apps/safe-claiming-app/abis/SafeToken.json +++ /dev/null @@ -1,408 +0,0 @@ -{ - "_format": "hh-sol-artifact-1", - "contractName": "SafeToken", - "sourceName": "safe-token/contracts/SafeToken.sol", - "abi": [ - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - } - ], - "stateMutability": "nonpayable", - "type": "constructor" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Approval", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "previousOwner", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "OwnershipTransferred", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "Paused", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": true, - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "indexed": true, - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "indexed": false, - "internalType": "uint256", - "name": "value", - "type": "uint256" - } - ], - "name": "Transfer", - "type": "event" - }, - { - "anonymous": false, - "inputs": [ - { - "indexed": false, - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "Unpaused", - "type": "event" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "owner", - "type": "address" - }, - { - "internalType": "address", - "name": "spender", - "type": "address" - } - ], - "name": "allowance", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "approve", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "account", - "type": "address" - } - ], - "name": "balanceOf", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "decimals", - "outputs": [ - { - "internalType": "uint8", - "name": "", - "type": "uint8" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "subtractedValue", - "type": "uint256" - } - ], - "name": "decreaseAllowance", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "spender", - "type": "address" - }, - { - "internalType": "uint256", - "name": "addedValue", - "type": "uint256" - } - ], - "name": "increaseAllowance", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "name", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "owner", - "outputs": [ - { - "internalType": "address", - "name": "", - "type": "address" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "paused", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "renounceOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "contract IERC20", - "name": "token", - "type": "address" - }, - { - "internalType": "address", - "name": "beneficiary", - "type": "address" - } - ], - "name": "rescueToken", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "symbol", - "outputs": [ - { - "internalType": "string", - "name": "", - "type": "string" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [], - "name": "totalSupply", - "outputs": [ - { - "internalType": "uint256", - "name": "", - "type": "uint256" - } - ], - "stateMutability": "view", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transfer", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "from", - "type": "address" - }, - { - "internalType": "address", - "name": "to", - "type": "address" - }, - { - "internalType": "uint256", - "name": "amount", - "type": "uint256" - } - ], - "name": "transferFrom", - "outputs": [ - { - "internalType": "bool", - "name": "", - "type": "bool" - } - ], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "internalType": "address", - "name": "newOwner", - "type": "address" - } - ], - "name": "transferOwnership", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [], - "name": "unpause", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } - ], - "bytecode": "0x60806040523480156200001157600080fd5b5060405162002b7538038062002b758339818101604052810190620000379190620006a0565b6040518060400160405280600a81526020017f5361666520546f6b656e000000000000000000000000000000000000000000008152506040518060400160405280600481526020017f53414645000000000000000000000000000000000000000000000000000000008152508160039080519060200190620000bb929190620005d9565b508060049080519060200190620000d4929190620005d9565b5050506000600560006101000a81548160ff02191690831515021790555062000112620001066200015860201b60201c565b6200016060201b60201c565b62000123816200016060201b60201c565b62000141816b033b2e3c9fd0803ce80000006200022660201b60201c565b620001516200039f60201b60201c565b5062000a96565b600033905090565b6000600560019054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905081600560016101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff16141562000299576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040162000290906200080d565b60405180910390fd5b620002ad600083836200045760201b60201c565b8060026000828254620002c191906200085d565b92505081905550806000808473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008282546200031891906200085d565b925050819055508173ffffffffffffffffffffffffffffffffffffffff16600073ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef836040516200037f91906200082f565b60405180910390a36200039b600083836200058e60201b60201c565b5050565b620003af6200059360201b60201c565b15620003f2576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620003e990620007c9565b60405180910390fd5b6001600560006101000a81548160ff0219169083151502179055507f62e78cea01bee320cd4e420270b5ea74000d11b0c9f74754ebdbfc544b05a2586200043e6200015860201b60201c565b6040516200044d91906200078a565b60405180910390a1565b6200046f838383620005aa60201b62000c251760201c565b3073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415620004e1576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401620004d890620007eb565b60405180910390fd5b620004f16200059360201b60201c565b1580620005475750620005096200015860201b60201c565b73ffffffffffffffffffffffffffffffffffffffff166200052f620005af60201b60201c565b73ffffffffffffffffffffffffffffffffffffffff16145b62000589576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016200058090620007a7565b60405180910390fd5b505050565b505050565b6000600560009054906101000a900460ff16905090565b505050565b6000600560019054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b828054620005e790620008f8565b90600052602060002090601f0160209004810192826200060b576000855562000657565b82601f106200062657805160ff191683800117855562000657565b8280016001018555821562000657579182015b828111156200065657825182559160200191906001019062000639565b5b5090506200066691906200066a565b5090565b5b80821115620006855760008160009055506001016200066b565b5090565b6000815190506200069a8162000a7c565b92915050565b600060208284031215620006b357600080fd5b6000620006c38482850162000689565b91505092915050565b620006d781620008ba565b82525050565b6000620006ec6026836200084c565b9150620006f9826200098c565b604082019050919050565b6000620007136010836200084c565b91506200072082620009db565b602082019050919050565b60006200073a6033836200084c565b9150620007478262000a04565b604082019050919050565b600062000761601f836200084c565b91506200076e8262000a53565b602082019050919050565b6200078481620008ee565b82525050565b6000602082019050620007a16000830184620006cc565b92915050565b60006020820190508181036000830152620007c281620006dd565b9050919050565b60006020820190508181036000830152620007e48162000704565b9050919050565b6000602082019050818103600083015262000806816200072b565b9050919050565b60006020820190508181036000830152620008288162000752565b9050919050565b600060208201905062000846600083018462000779565b92915050565b600082825260208201905092915050565b60006200086a82620008ee565b91506200087783620008ee565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115620008af57620008ae6200092e565b5b828201905092915050565b6000620008c782620008ce565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060028204905060018216806200091157607f821691505b602082108114156200092857620009276200095d565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b7f53616665546f6b656e3a20746f6b656e207472616e73666572207768696c652060008201527f7061757365640000000000000000000000000000000000000000000000000000602082015250565b7f5061757361626c653a2070617573656400000000000000000000000000000000600082015250565b7f53616665546f6b656e3a2063616e6e6f74207472616e7366657220746f6b656e60008201527f7320746f20746f6b656e20636f6e747261637400000000000000000000000000602082015250565b7f45524332303a206d696e7420746f20746865207a65726f206164647265737300600082015250565b62000a8781620008ba565b811462000a9357600080fd5b50565b6120cf8062000aa66000396000f3fe608060405234801561001057600080fd5b506004361061010b5760003560e01c80635c975abb116100a257806395d89b411161007157806395d89b4114610296578063a457c2d7146102b4578063a9059cbb146102e4578063dd62ed3e14610314578063f2fde38b146103445761010b565b80635c975abb1461022057806370a082311461023e578063715018a61461026e5780638da5cb5b146102785761010b565b8063313ce567116100de578063313ce567146101ac57806339509351146101ca5780633f4ba83a146101fa5780634707d000146102045761010b565b806306fdde0314610110578063095ea7b31461012e57806318160ddd1461015e57806323b872dd1461017c575b600080fd5b610118610360565b604051610125919061184f565b60405180910390f35b610148600480360381019061014391906114a4565b6103f2565b6040516101559190611834565b60405180910390f35b610166610415565b6040516101739190611a51565b60405180910390f35b61019660048036038101906101919190611455565b61041f565b6040516101a39190611834565b60405180910390f35b6101b461044e565b6040516101c19190611a6c565b60405180910390f35b6101e460048036038101906101df91906114a4565b610457565b6040516101f19190611834565b60405180910390f35b610202610501565b005b61021e60048036038101906102199190611509565b6105ce565b005b6102286107f6565b6040516102359190611834565b60405180910390f35b610258600480360381019061025391906113f0565b61080d565b6040516102659190611a51565b60405180910390f35b610276610855565b005b6102806108dd565b60405161028d91906117f0565b60405180910390f35b61029e610907565b6040516102ab919061184f565b60405180910390f35b6102ce60048036038101906102c991906114a4565b610999565b6040516102db9190611834565b60405180910390f35b6102fe60048036038101906102f991906114a4565b610a83565b60405161030b9190611834565b60405180910390f35b61032e60048036038101906103299190611419565b610aa6565b60405161033b9190611a51565b60405180910390f35b61035e600480360381019061035991906113f0565b610b2d565b005b60606003805461036f90611b93565b80601f016020809104026020016040519081016040528092919081815260200182805461039b90611b93565b80156103e85780601f106103bd576101008083540402835291602001916103e8565b820191906000526020600020905b8154815290600101906020018083116103cb57829003601f168201915b5050505050905090565b6000806103fd610c2a565b905061040a818585610c32565b600191505092915050565b6000600254905090565b60008061042a610c2a565b9050610437858285610dfd565b610442858585610e89565b60019150509392505050565b60006012905090565b600080610462610c2a565b90506104f6818585600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546104f19190611aa3565b610c32565b600191505092915050565b610509610c2a565b73ffffffffffffffffffffffffffffffffffffffff166105276108dd565b73ffffffffffffffffffffffffffffffffffffffff161461057d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610574906119b1565b60405180910390fd5b6105856107f6565b6105c4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105bb90611951565b60405180910390fd5b6105cc61110a565b565b6105d6610c2a565b73ffffffffffffffffffffffffffffffffffffffff166105f46108dd565b73ffffffffffffffffffffffffffffffffffffffff161461064a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610641906119b1565b60405180910390fd5b60008273ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b815260040161068591906117f0565b60206040518083038186803b15801561069d57600080fd5b505afa1580156106b1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106d59190611545565b90506000811161071a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161071190611931565b60405180910390fd5b6107258383836111ac565b8273ffffffffffffffffffffffffffffffffffffffff1663a9059cbb83836040518363ffffffff1660e01b815260040161076092919061180b565b602060405180830381600087803b15801561077a57600080fd5b505af115801561078e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107b291906114e0565b6107f1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107e8906118d1565b60405180910390fd5b505050565b6000600560009054906101000a900460ff16905090565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b61085d610c2a565b73ffffffffffffffffffffffffffffffffffffffff1661087b6108dd565b73ffffffffffffffffffffffffffffffffffffffff16146108d1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108c8906119b1565b60405180910390fd5b6108db60006111b1565b565b6000600560019054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60606004805461091690611b93565b80601f016020809104026020016040519081016040528092919081815260200182805461094290611b93565b801561098f5780601f106109645761010080835404028352916020019161098f565b820191906000526020600020905b81548152906001019060200180831161097257829003601f168201915b5050505050905090565b6000806109a4610c2a565b90506000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905083811015610a6a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a6190611a31565b60405180910390fd5b610a778286868403610c32565b60019250505092915050565b600080610a8e610c2a565b9050610a9b818585610e89565b600191505092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b610b35610c2a565b73ffffffffffffffffffffffffffffffffffffffff16610b536108dd565b73ffffffffffffffffffffffffffffffffffffffff1614610ba9576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ba0906119b1565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415610c19576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610c10906118f1565b60405180910390fd5b610c22816111b1565b50565b505050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610ca2576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610c99906119f1565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610d12576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d0990611911565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92583604051610df09190611a51565b60405180910390a3505050565b6000610e098484610aa6565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114610e835781811015610e75576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e6c90611971565b60405180910390fd5b610e828484848403610c32565b5b50505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610ef9576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ef0906119d1565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610f69576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f6090611871565b60405180910390fd5b610f74838383611277565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015610ffa576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ff190611991565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461108d9190611aa3565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516110f19190611a51565b60405180910390a3611104848484611382565b50505050565b6111126107f6565b611151576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161114890611891565b60405180910390fd5b6000600560006101000a81548160ff0219169083151502179055507f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa611195610c2a565b6040516111a291906117f0565b60405180910390a1565b505050565b6000600560019054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905081600560016101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b611282838383610c25565b3073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156112f1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016112e890611a11565b60405180910390fd5b6112f96107f6565b158061133e5750611308610c2a565b73ffffffffffffffffffffffffffffffffffffffff166113266108dd565b73ffffffffffffffffffffffffffffffffffffffff16145b61137d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611374906118b1565b60405180910390fd5b505050565b505050565b6000813590506113968161203d565b92915050565b6000815190506113ab81612054565b92915050565b6000813590506113c08161206b565b92915050565b6000813590506113d581612082565b92915050565b6000815190506113ea81612082565b92915050565b60006020828403121561140257600080fd5b600061141084828501611387565b91505092915050565b6000806040838503121561142c57600080fd5b600061143a85828601611387565b925050602061144b85828601611387565b9150509250929050565b60008060006060848603121561146a57600080fd5b600061147886828701611387565b935050602061148986828701611387565b925050604061149a868287016113c6565b9150509250925092565b600080604083850312156114b757600080fd5b60006114c585828601611387565b92505060206114d6858286016113c6565b9150509250929050565b6000602082840312156114f257600080fd5b60006115008482850161139c565b91505092915050565b6000806040838503121561151c57600080fd5b600061152a858286016113b1565b925050602061153b85828601611387565b9150509250929050565b60006020828403121561155757600080fd5b6000611565848285016113db565b91505092915050565b61157781611af9565b82525050565b61158681611b0b565b82525050565b600061159782611a87565b6115a18185611a92565b93506115b1818560208601611b60565b6115ba81611c23565b840191505092915050565b60006115d2602383611a92565b91506115dd82611c34565b604082019050919050565b60006115f5601483611a92565b915061160082611c83565b602082019050919050565b6000611618602683611a92565b915061162382611cac565b604082019050919050565b600061163b602483611a92565b915061164682611cfb565b604082019050919050565b600061165e602683611a92565b915061166982611d4a565b604082019050919050565b6000611681602283611a92565b915061168c82611d99565b604082019050919050565b60006116a4602183611a92565b91506116af82611de8565b604082019050919050565b60006116c7601e83611a92565b91506116d282611e37565b602082019050919050565b60006116ea601d83611a92565b91506116f582611e60565b602082019050919050565b600061170d602683611a92565b915061171882611e89565b604082019050919050565b6000611730602083611a92565b915061173b82611ed8565b602082019050919050565b6000611753602583611a92565b915061175e82611f01565b604082019050919050565b6000611776602483611a92565b915061178182611f50565b604082019050919050565b6000611799603383611a92565b91506117a482611f9f565b604082019050919050565b60006117bc602583611a92565b91506117c782611fee565b604082019050919050565b6117db81611b49565b82525050565b6117ea81611b53565b82525050565b6000602082019050611805600083018461156e565b92915050565b6000604082019050611820600083018561156e565b61182d60208301846117d2565b9392505050565b6000602082019050611849600083018461157d565b92915050565b60006020820190508181036000830152611869818461158c565b905092915050565b6000602082019050818103600083015261188a816115c5565b9050919050565b600060208201905081810360008301526118aa816115e8565b9050919050565b600060208201905081810360008301526118ca8161160b565b9050919050565b600060208201905081810360008301526118ea8161162e565b9050919050565b6000602082019050818103600083015261190a81611651565b9050919050565b6000602082019050818103600083015261192a81611674565b9050919050565b6000602082019050818103600083015261194a81611697565b9050919050565b6000602082019050818103600083015261196a816116ba565b9050919050565b6000602082019050818103600083015261198a816116dd565b9050919050565b600060208201905081810360008301526119aa81611700565b9050919050565b600060208201905081810360008301526119ca81611723565b9050919050565b600060208201905081810360008301526119ea81611746565b9050919050565b60006020820190508181036000830152611a0a81611769565b9050919050565b60006020820190508181036000830152611a2a8161178c565b9050919050565b60006020820190508181036000830152611a4a816117af565b9050919050565b6000602082019050611a6660008301846117d2565b92915050565b6000602082019050611a8160008301846117e1565b92915050565b600081519050919050565b600082825260208201905092915050565b6000611aae82611b49565b9150611ab983611b49565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115611aee57611aed611bc5565b5b828201905092915050565b6000611b0482611b29565b9050919050565b60008115159050919050565b6000611b2282611af9565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b60005b83811015611b7e578082015181840152602081019050611b63565b83811115611b8d576000848401525b50505050565b60006002820490506001821680611bab57607f821691505b60208210811415611bbf57611bbe611bf4565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000601f19601f8301169050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b7f5061757361626c653a206e6f7420706175736564000000000000000000000000600082015250565b7f53616665546f6b656e3a20746f6b656e207472616e73666572207768696c652060008201527f7061757365640000000000000000000000000000000000000000000000000000602082015250565b7f546f6b656e526573637565723a20436f756c64206e6f7420726573637565207460008201527f6f6b656e00000000000000000000000000000000000000000000000000000000602082015250565b7f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160008201527f6464726573730000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b7f546f6b656e526573637565723a204e6f20746f6b656e7320746f20726573637560008201527f6500000000000000000000000000000000000000000000000000000000000000602082015250565b7f53616665546f6b656e3a20746f6b656e206973206e6f74207061757365640000600082015250565b7f45524332303a20696e73756666696369656e7420616c6c6f77616e6365000000600082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b7f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572600082015250565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b7f53616665546f6b656e3a2063616e6e6f74207472616e7366657220746f6b656e60008201527f7320746f20746f6b656e20636f6e747261637400000000000000000000000000602082015250565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b61204681611af9565b811461205157600080fd5b50565b61205d81611b0b565b811461206857600080fd5b50565b61207481611b17565b811461207f57600080fd5b50565b61208b81611b49565b811461209657600080fd5b5056fea2646970667358221220655322be7fb2309d30824c04699ad2dbe661fddf7f54ca0e84ae486c61358af364736f6c63430008040033", - "deployedBytecode": "0x608060405234801561001057600080fd5b506004361061010b5760003560e01c80635c975abb116100a257806395d89b411161007157806395d89b4114610296578063a457c2d7146102b4578063a9059cbb146102e4578063dd62ed3e14610314578063f2fde38b146103445761010b565b80635c975abb1461022057806370a082311461023e578063715018a61461026e5780638da5cb5b146102785761010b565b8063313ce567116100de578063313ce567146101ac57806339509351146101ca5780633f4ba83a146101fa5780634707d000146102045761010b565b806306fdde0314610110578063095ea7b31461012e57806318160ddd1461015e57806323b872dd1461017c575b600080fd5b610118610360565b604051610125919061184f565b60405180910390f35b610148600480360381019061014391906114a4565b6103f2565b6040516101559190611834565b60405180910390f35b610166610415565b6040516101739190611a51565b60405180910390f35b61019660048036038101906101919190611455565b61041f565b6040516101a39190611834565b60405180910390f35b6101b461044e565b6040516101c19190611a6c565b60405180910390f35b6101e460048036038101906101df91906114a4565b610457565b6040516101f19190611834565b60405180910390f35b610202610501565b005b61021e60048036038101906102199190611509565b6105ce565b005b6102286107f6565b6040516102359190611834565b60405180910390f35b610258600480360381019061025391906113f0565b61080d565b6040516102659190611a51565b60405180910390f35b610276610855565b005b6102806108dd565b60405161028d91906117f0565b60405180910390f35b61029e610907565b6040516102ab919061184f565b60405180910390f35b6102ce60048036038101906102c991906114a4565b610999565b6040516102db9190611834565b60405180910390f35b6102fe60048036038101906102f991906114a4565b610a83565b60405161030b9190611834565b60405180910390f35b61032e60048036038101906103299190611419565b610aa6565b60405161033b9190611a51565b60405180910390f35b61035e600480360381019061035991906113f0565b610b2d565b005b60606003805461036f90611b93565b80601f016020809104026020016040519081016040528092919081815260200182805461039b90611b93565b80156103e85780601f106103bd576101008083540402835291602001916103e8565b820191906000526020600020905b8154815290600101906020018083116103cb57829003601f168201915b5050505050905090565b6000806103fd610c2a565b905061040a818585610c32565b600191505092915050565b6000600254905090565b60008061042a610c2a565b9050610437858285610dfd565b610442858585610e89565b60019150509392505050565b60006012905090565b600080610462610c2a565b90506104f6818585600160008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008973ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020546104f19190611aa3565b610c32565b600191505092915050565b610509610c2a565b73ffffffffffffffffffffffffffffffffffffffff166105276108dd565b73ffffffffffffffffffffffffffffffffffffffff161461057d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610574906119b1565b60405180910390fd5b6105856107f6565b6105c4576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016105bb90611951565b60405180910390fd5b6105cc61110a565b565b6105d6610c2a565b73ffffffffffffffffffffffffffffffffffffffff166105f46108dd565b73ffffffffffffffffffffffffffffffffffffffff161461064a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610641906119b1565b60405180910390fd5b60008273ffffffffffffffffffffffffffffffffffffffff166370a08231306040518263ffffffff1660e01b815260040161068591906117f0565b60206040518083038186803b15801561069d57600080fd5b505afa1580156106b1573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906106d59190611545565b90506000811161071a576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161071190611931565b60405180910390fd5b6107258383836111ac565b8273ffffffffffffffffffffffffffffffffffffffff1663a9059cbb83836040518363ffffffff1660e01b815260040161076092919061180b565b602060405180830381600087803b15801561077a57600080fd5b505af115801561078e573d6000803e3d6000fd5b505050506040513d601f19601f820116820180604052508101906107b291906114e0565b6107f1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016107e8906118d1565b60405180910390fd5b505050565b6000600560009054906101000a900460ff16905090565b60008060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020549050919050565b61085d610c2a565b73ffffffffffffffffffffffffffffffffffffffff1661087b6108dd565b73ffffffffffffffffffffffffffffffffffffffff16146108d1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016108c8906119b1565b60405180910390fd5b6108db60006111b1565b565b6000600560019054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905090565b60606004805461091690611b93565b80601f016020809104026020016040519081016040528092919081815260200182805461094290611b93565b801561098f5780601f106109645761010080835404028352916020019161098f565b820191906000526020600020905b81548152906001019060200180831161097257829003601f168201915b5050505050905090565b6000806109a4610c2a565b90506000600160008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905083811015610a6a576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610a6190611a31565b60405180910390fd5b610a778286868403610c32565b60019250505092915050565b600080610a8e610c2a565b9050610a9b818585610e89565b600191505092915050565b6000600160008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008373ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905092915050565b610b35610c2a565b73ffffffffffffffffffffffffffffffffffffffff16610b536108dd565b73ffffffffffffffffffffffffffffffffffffffff1614610ba9576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ba0906119b1565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff161415610c19576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610c10906118f1565b60405180910390fd5b610c22816111b1565b50565b505050565b600033905090565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610ca2576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610c99906119f1565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610d12576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610d0990611911565b60405180910390fd5b80600160008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002060008473ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020819055508173ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff167f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b92583604051610df09190611a51565b60405180910390a3505050565b6000610e098484610aa6565b90507fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff8114610e835781811015610e75576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610e6c90611971565b60405180910390fd5b610e828484848403610c32565b5b50505050565b600073ffffffffffffffffffffffffffffffffffffffff168373ffffffffffffffffffffffffffffffffffffffff161415610ef9576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ef0906119d1565b60405180910390fd5b600073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff161415610f69576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610f6090611871565b60405180910390fd5b610f74838383611277565b60008060008573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002054905081811015610ffa576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401610ff190611991565b60405180910390fd5b8181036000808673ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200190815260200160002081905550816000808573ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff168152602001908152602001600020600082825461108d9190611aa3565b925050819055508273ffffffffffffffffffffffffffffffffffffffff168473ffffffffffffffffffffffffffffffffffffffff167fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef846040516110f19190611a51565b60405180910390a3611104848484611382565b50505050565b6111126107f6565b611151576040517f08c379a000000000000000000000000000000000000000000000000000000000815260040161114890611891565b60405180910390fd5b6000600560006101000a81548160ff0219169083151502179055507f5db9ee0a495bf2e6ff9c91a7834c1ba4fdd244a5e8aa4e537bd38aeae4b073aa611195610c2a565b6040516111a291906117f0565b60405180910390a1565b505050565b6000600560019054906101000a900473ffffffffffffffffffffffffffffffffffffffff16905081600560016101000a81548173ffffffffffffffffffffffffffffffffffffffff021916908373ffffffffffffffffffffffffffffffffffffffff1602179055508173ffffffffffffffffffffffffffffffffffffffff168173ffffffffffffffffffffffffffffffffffffffff167f8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e060405160405180910390a35050565b611282838383610c25565b3073ffffffffffffffffffffffffffffffffffffffff168273ffffffffffffffffffffffffffffffffffffffff1614156112f1576040517f08c379a00000000000000000000000000000000000000000000000000000000081526004016112e890611a11565b60405180910390fd5b6112f96107f6565b158061133e5750611308610c2a565b73ffffffffffffffffffffffffffffffffffffffff166113266108dd565b73ffffffffffffffffffffffffffffffffffffffff16145b61137d576040517f08c379a0000000000000000000000000000000000000000000000000000000008152600401611374906118b1565b60405180910390fd5b505050565b505050565b6000813590506113968161203d565b92915050565b6000815190506113ab81612054565b92915050565b6000813590506113c08161206b565b92915050565b6000813590506113d581612082565b92915050565b6000815190506113ea81612082565b92915050565b60006020828403121561140257600080fd5b600061141084828501611387565b91505092915050565b6000806040838503121561142c57600080fd5b600061143a85828601611387565b925050602061144b85828601611387565b9150509250929050565b60008060006060848603121561146a57600080fd5b600061147886828701611387565b935050602061148986828701611387565b925050604061149a868287016113c6565b9150509250925092565b600080604083850312156114b757600080fd5b60006114c585828601611387565b92505060206114d6858286016113c6565b9150509250929050565b6000602082840312156114f257600080fd5b60006115008482850161139c565b91505092915050565b6000806040838503121561151c57600080fd5b600061152a858286016113b1565b925050602061153b85828601611387565b9150509250929050565b60006020828403121561155757600080fd5b6000611565848285016113db565b91505092915050565b61157781611af9565b82525050565b61158681611b0b565b82525050565b600061159782611a87565b6115a18185611a92565b93506115b1818560208601611b60565b6115ba81611c23565b840191505092915050565b60006115d2602383611a92565b91506115dd82611c34565b604082019050919050565b60006115f5601483611a92565b915061160082611c83565b602082019050919050565b6000611618602683611a92565b915061162382611cac565b604082019050919050565b600061163b602483611a92565b915061164682611cfb565b604082019050919050565b600061165e602683611a92565b915061166982611d4a565b604082019050919050565b6000611681602283611a92565b915061168c82611d99565b604082019050919050565b60006116a4602183611a92565b91506116af82611de8565b604082019050919050565b60006116c7601e83611a92565b91506116d282611e37565b602082019050919050565b60006116ea601d83611a92565b91506116f582611e60565b602082019050919050565b600061170d602683611a92565b915061171882611e89565b604082019050919050565b6000611730602083611a92565b915061173b82611ed8565b602082019050919050565b6000611753602583611a92565b915061175e82611f01565b604082019050919050565b6000611776602483611a92565b915061178182611f50565b604082019050919050565b6000611799603383611a92565b91506117a482611f9f565b604082019050919050565b60006117bc602583611a92565b91506117c782611fee565b604082019050919050565b6117db81611b49565b82525050565b6117ea81611b53565b82525050565b6000602082019050611805600083018461156e565b92915050565b6000604082019050611820600083018561156e565b61182d60208301846117d2565b9392505050565b6000602082019050611849600083018461157d565b92915050565b60006020820190508181036000830152611869818461158c565b905092915050565b6000602082019050818103600083015261188a816115c5565b9050919050565b600060208201905081810360008301526118aa816115e8565b9050919050565b600060208201905081810360008301526118ca8161160b565b9050919050565b600060208201905081810360008301526118ea8161162e565b9050919050565b6000602082019050818103600083015261190a81611651565b9050919050565b6000602082019050818103600083015261192a81611674565b9050919050565b6000602082019050818103600083015261194a81611697565b9050919050565b6000602082019050818103600083015261196a816116ba565b9050919050565b6000602082019050818103600083015261198a816116dd565b9050919050565b600060208201905081810360008301526119aa81611700565b9050919050565b600060208201905081810360008301526119ca81611723565b9050919050565b600060208201905081810360008301526119ea81611746565b9050919050565b60006020820190508181036000830152611a0a81611769565b9050919050565b60006020820190508181036000830152611a2a8161178c565b9050919050565b60006020820190508181036000830152611a4a816117af565b9050919050565b6000602082019050611a6660008301846117d2565b92915050565b6000602082019050611a8160008301846117e1565b92915050565b600081519050919050565b600082825260208201905092915050565b6000611aae82611b49565b9150611ab983611b49565b9250827fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff03821115611aee57611aed611bc5565b5b828201905092915050565b6000611b0482611b29565b9050919050565b60008115159050919050565b6000611b2282611af9565b9050919050565b600073ffffffffffffffffffffffffffffffffffffffff82169050919050565b6000819050919050565b600060ff82169050919050565b60005b83811015611b7e578082015181840152602081019050611b63565b83811115611b8d576000848401525b50505050565b60006002820490506001821680611bab57607f821691505b60208210811415611bbf57611bbe611bf4565b5b50919050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b7f4e487b7100000000000000000000000000000000000000000000000000000000600052602260045260246000fd5b6000601f19601f8301169050919050565b7f45524332303a207472616e7366657220746f20746865207a65726f206164647260008201527f6573730000000000000000000000000000000000000000000000000000000000602082015250565b7f5061757361626c653a206e6f7420706175736564000000000000000000000000600082015250565b7f53616665546f6b656e3a20746f6b656e207472616e73666572207768696c652060008201527f7061757365640000000000000000000000000000000000000000000000000000602082015250565b7f546f6b656e526573637565723a20436f756c64206e6f7420726573637565207460008201527f6f6b656e00000000000000000000000000000000000000000000000000000000602082015250565b7f4f776e61626c653a206e6577206f776e657220697320746865207a65726f206160008201527f6464726573730000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f766520746f20746865207a65726f20616464726560008201527f7373000000000000000000000000000000000000000000000000000000000000602082015250565b7f546f6b656e526573637565723a204e6f20746f6b656e7320746f20726573637560008201527f6500000000000000000000000000000000000000000000000000000000000000602082015250565b7f53616665546f6b656e3a20746f6b656e206973206e6f74207061757365640000600082015250565b7f45524332303a20696e73756666696369656e7420616c6c6f77616e6365000000600082015250565b7f45524332303a207472616e7366657220616d6f756e742065786365656473206260008201527f616c616e63650000000000000000000000000000000000000000000000000000602082015250565b7f4f776e61626c653a2063616c6c6572206973206e6f7420746865206f776e6572600082015250565b7f45524332303a207472616e736665722066726f6d20746865207a65726f20616460008201527f6472657373000000000000000000000000000000000000000000000000000000602082015250565b7f45524332303a20617070726f76652066726f6d20746865207a65726f2061646460008201527f7265737300000000000000000000000000000000000000000000000000000000602082015250565b7f53616665546f6b656e3a2063616e6e6f74207472616e7366657220746f6b656e60008201527f7320746f20746f6b656e20636f6e747261637400000000000000000000000000602082015250565b7f45524332303a2064656372656173656420616c6c6f77616e63652062656c6f7760008201527f207a65726f000000000000000000000000000000000000000000000000000000602082015250565b61204681611af9565b811461205157600080fd5b50565b61205d81611b0b565b811461206857600080fd5b50565b61207481611b17565b811461207f57600080fd5b50565b61208b81611b49565b811461209657600080fd5b5056fea2646970667358221220655322be7fb2309d30824c04699ad2dbe661fddf7f54ca0e84ae486c61358af364736f6c63430008040033", - "linkReferences": {}, - "deployedLinkReferences": {} -} diff --git a/apps/safe-claiming-app/contracts/deps.sol b/apps/safe-claiming-app/contracts/deps.sol deleted file mode 100644 index 89b2d8a52..000000000 --- a/apps/safe-claiming-app/contracts/deps.sol +++ /dev/null @@ -1,6 +0,0 @@ -// SPDX-License-Identifier: UNLICENSED -pragma solidity >=0.7.0 <0.9.0; - -// These two contract import all others in `safe-token` -import { Airdrop } from "safe-token/contracts/Airdrop.sol"; -import { SafeToken } from "safe-token/contracts/SafeToken.sol"; \ No newline at end of file diff --git a/apps/safe-claiming-app/craco.config.ts b/apps/safe-claiming-app/craco.config.ts deleted file mode 100644 index 1ba3b9483..000000000 --- a/apps/safe-claiming-app/craco.config.ts +++ /dev/null @@ -1,21 +0,0 @@ -import NodePolyfillPlugin from "node-polyfill-webpack-plugin" -import { Configuration } from "webpack/types.d" -const ModuleScopePlugin = require("react-dev-utils/ModuleScopePlugin") - -const config = { - webpack: { - plugins: [new NodePolyfillPlugin()], - configure: (webpackConfig: Configuration) => { - if (!webpackConfig?.resolve?.plugins) return webpackConfig - - // Allow imports outside of src/ - webpackConfig.resolve.plugins = webpackConfig.resolve.plugins.filter( - ({ constructor }: InstanceType) => - constructor?.name !== "ModuleScopePlugin" - ) - return webpackConfig - }, - }, -} - -export default config diff --git a/apps/safe-claiming-app/package.json b/apps/safe-claiming-app/package.json deleted file mode 100644 index 49de30f85..000000000 --- a/apps/safe-claiming-app/package.json +++ /dev/null @@ -1,63 +0,0 @@ -{ - "name": "safe-claiming-app", - "version": "0.5.0", - "private": true, - "homepage": "/safe-claiming-app", - "dependencies": { - "@craco/craco": "^6.4.3", - "@emotion/react": "^11.10.5", - "@emotion/styled": "^11.10.5", - "@mui/icons-material": "^5.10.9", - "@mui/material": "^5.10.12", - "@safe-global/safe-apps-provider": "^0.16.0", - "bezier-easing": "^2.1.0", - "ethers": "^5.7.2", - "react-twitter-embed": "^4.0.4" - }, - "devDependencies": { - "@typechain/ethers-v5": "^8.0.0", - "node-polyfill-webpack-plugin": "^1.1.4", - "typechain": "^8.1.1" - }, - "scripts": { - "start": "craco start", - "build": "craco build", - "test": "craco test", - "eject": "react-scripts eject", - "lint": "eslint . --ext .js,.jsx,.ts,.tsx", - "lint:fix": "eslint . --ext .js,.jsx,.ts,.tsx --fix", - "generate-types": "yarn generate-types:safeToken", - "generate-types:safeToken": "typechain --target ethers-v5 --out-dir ./src/types/contracts './abis/*.json'", - "compile": "yarn run generate-types", - "postinstall": "yarn compile", - "deploy:s3": "bash ../../scripts/deploy_to_s3_bucket.sh", - "deploy:pr": "bash ../../scripts/deploy_pr.sh", - "deploy:prod-hook": "bash ../../scripts/prepare_production_deployment.sh" - }, - "eslintConfig": { - "extends": [ - "react-app", - "react-app/jest" - ] - }, - "jest": { - "collectCoverageFrom": [ - "**/*.{ts,tsx}", - "!**/node_modules/**", - "!**/types/**", - "!**/*.d.ts" - ] - }, - "browserslist": { - "production": [ - ">0.2%", - "not dead", - "not op_mini all" - ], - "development": [ - "last 1 chrome version", - "last 1 firefox version", - "last 1 safari version" - ] - } -} diff --git a/apps/safe-claiming-app/project.json b/apps/safe-claiming-app/project.json deleted file mode 100644 index 4a87a979b..000000000 --- a/apps/safe-claiming-app/project.json +++ /dev/null @@ -1,21 +0,0 @@ -{ - "root": "apps/safe-claiming-app/", - "sourceRoot": "apps/safe-claiming-app/src/", - "projectType": "application", - "tags": ["scope:applications"], - "targets": { - "version": { - "executor": "@jscutlery/semver:version", - "options": { - "commitMessageFormat": "chore(${projectName}): release version ${version}" - } - }, - "github": { - "executor": "@jscutlery/semver:github", - "options": { - "tag": "${tag}", - "generateNotes": true - } - } - } -} diff --git a/apps/safe-claiming-app/public/index.html b/apps/safe-claiming-app/public/index.html deleted file mode 100644 index df789d28d..000000000 --- a/apps/safe-claiming-app/public/index.html +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - $SAFE Claiming App - - - -
- - diff --git a/apps/safe-claiming-app/public/logo.svg b/apps/safe-claiming-app/public/logo.svg deleted file mode 100644 index c3b045a37..000000000 --- a/apps/safe-claiming-app/public/logo.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/apps/safe-claiming-app/public/manifest.json b/apps/safe-claiming-app/public/manifest.json deleted file mode 100644 index d304760f6..000000000 --- a/apps/safe-claiming-app/public/manifest.json +++ /dev/null @@ -1,17 +0,0 @@ -{ - "short_name": "safe-claiming-app", - "name": "$SAFE Claiming App", - "description": "Claim your $SAFE Tokens", - "iconPath": "", - "icons": [ - { - "src": "logo.svg", - "sizes": "any", - "type": "image/svg+xml" - } - ], - "start_url": ".", - "display": "", - "theme_color": "", - "background_color": "" -} diff --git a/apps/safe-claiming-app/src/App.tsx b/apps/safe-claiming-app/src/App.tsx deleted file mode 100644 index e8d5cdecb..000000000 --- a/apps/safe-claiming-app/src/App.tsx +++ /dev/null @@ -1,236 +0,0 @@ -import { useSafeAppsSDK } from "@safe-global/safe-apps-react-sdk" -import { styled, useTheme } from "@mui/material" -import { - lazy, - ReactElement, - Suspense, - useEffect, - useMemo, - useState, -} from "react" - -import { ProgressBar } from "src/components/helpers/ProgressBar" -import { FloatingTiles } from "./components/helpers/FloatingTiles" -import { Loading } from "./components/helpers/Loading" -import { ScrollContextProvider } from "./components/helpers/ScrollContext" -import { UnexpectedError } from "./components/helpers/UnexpectedError" -import { UnsupportedNetwork } from "./components/helpers/UnsupportedNetwork" -import { Chains } from "./config/constants" -import { useDelegate } from "./hooks/useDelegate" -import { useDelegatesFile, DelegateEntry } from "./hooks/useDelegatesFile" -import { useIsTokenPaused } from "./hooks/useIsTokenPaused" -import useSafeTokenAllocation, { Vesting } from "./hooks/useSafeTokenAllocation" -import { sameAddress } from "./utils/addresses" - -const Container = styled("div")` - width: 650px; - padding-top: 24px; - position: relative; - margin: auto; -` - -const Circle = styled("div")` - width: 450px; - height: 450px; - position: absolute; - border-radius: 50%; - z-index: -1; - right: -100px; - bottom: -100px; - background: radial-gradient(circle, #5fddff 0%, rgb(246 247 248 / 0%) 70%); -` - -const Circle2 = styled("div")` - width: 350px; - height: 350px; - position: absolute; - border-radius: 50%; - z-index: -1; - left: -100px; - top: 50px; - background: radial-gradient( - circle, - ${(props) => props.theme.palette.safeGreen.main} 0%, - rgb(246 247 248 / 0%) 70% - ); -` -const EDUCATION_END = 5 -const CLAIM_STEP = EDUCATION_END + 2 -const SUCCESS_STEP = EDUCATION_END + 3 -const NO_AIRDROP_STEP = EDUCATION_END + 4 - -const steps = [ - lazy(() => import("src/components/steps/Intro")), - - // Educational Series - lazy(() => import("src/components/steps/SafeInfo")), - lazy(() => import("src/components/steps/Distribution")), - lazy(() => import("src/components/steps/Government")), - lazy(() => import("src/components/steps/SafeNavigation")), - lazy(() => import("src/components/steps/Disclaimer")), - - lazy(() => import("src/components/steps/Delegate")), - lazy(() => import("src/components/steps/Claim")), - lazy(() => import("src/components/steps/Success")), - lazy(() => import("src/components/steps/NoAirdrop")), -] - -export type AppState = { - vestingData: Vesting[] - isTokenPaused: boolean - delegateAddressFromContract?: string - delegateData: DelegateEntry[] - delegate?: DelegateEntry - claimedAmount?: string -} - -const initialState: AppState = { - vestingData: [], - isTokenPaused: true, - delegateData: [], -} - -const App = (): ReactElement => { - const { palette } = useTheme() - const [appState, setAppState] = useState(initialState) - - const { safe } = useSafeAppsSDK() - - const [safeTokenAllocation, allocationError, isVestingLoading] = - useSafeTokenAllocation() - - const validVestingData = useMemo( - () => - safeTokenAllocation?.vestingData.filter((vesting) => !vesting.isExpired), - [safeTokenAllocation?.vestingData] - ) - - const [delegates, , delegatesFileError] = useDelegatesFile() - const delegateAddressFromContract = useDelegate() - - const isTokenPaused = useIsTokenPaused() - - const currentDelegate = useMemo(() => { - if (appState.delegate) { - return appState.delegate - } - - if (delegateAddressFromContract) { - const registeredDelegateFromData = delegates.find((entry) => - sameAddress(entry.address, delegateAddressFromContract) - ) - return ( - registeredDelegateFromData || { address: delegateAddressFromContract } - ) - } - }, [appState.delegate, delegateAddressFromContract, delegates]) - - useEffect(() => { - setAppState((prev) => ({ - ...prev, - vestingData: validVestingData ?? prev.vestingData, - delegate: currentDelegate, - isTokenPaused, - delegateAddressFromContract, - delegateData: delegates, - })) - }, [ - validVestingData, - isTokenPaused, - delegates, - currentDelegate, - delegateAddressFromContract, - ]) - - const [activeStep, setActiveStep] = useState(0) - - // once the delegate is fetched from on-chain we update the activeStep - useEffect(() => { - if (delegateAddressFromContract) { - setActiveStep(CLAIM_STEP) - } - }, [delegateAddressFromContract]) - - useEffect(() => { - if ( - validVestingData && - validVestingData.length === 0 && - !isVestingLoading - ) { - setActiveStep(NO_AIRDROP_STEP) - } - }, [isVestingLoading, validVestingData]) - - const handleBack = () => { - if (activeStep === SUCCESS_STEP) { - // Go back to claiming instead of status - setActiveStep(CLAIM_STEP) - } else { - setActiveStep((prev) => prev - 1) - } - } - - const handleNext = () => { - setActiveStep((prev) => prev + 1) - } - - const Step = steps[activeStep] - - const hasNoAirdrop = activeStep === NO_AIRDROP_STEP - - const fatalError = delegatesFileError || allocationError - - const progress = - hasNoAirdrop || fatalError ? 0 : activeStep / (steps.length - 2) - - const unsupportedChain = - safe.chainId !== Chains.MAINNET && - safe.chainId !== Chains.RINKEBY && - safe.chainId !== Chains.GOERLI - - return ( - <> - - - - - {fatalError ? ( - - ) : unsupportedChain ? ( - - ) : ( -
- {isVestingLoading ? ( - - ) : ( - <> - {!hasNoAirdrop && } - - - - - )} -
- )} - {!hasNoAirdrop && } - {!hasNoAirdrop && } -
-
- - ) -} - -export default App diff --git a/apps/safe-claiming-app/src/assets/images/assets.svg b/apps/safe-claiming-app/src/assets/images/assets.svg deleted file mode 100644 index 5e2d44a97..000000000 --- a/apps/safe-claiming-app/src/assets/images/assets.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/apps/safe-claiming-app/src/assets/images/chain.svg b/apps/safe-claiming-app/src/assets/images/chain.svg deleted file mode 100644 index d213d97fd..000000000 --- a/apps/safe-claiming-app/src/assets/images/chain.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/apps/safe-claiming-app/src/assets/images/coins.png b/apps/safe-claiming-app/src/assets/images/coins.png deleted file mode 100644 index dae38c8d5..000000000 Binary files a/apps/safe-claiming-app/src/assets/images/coins.png and /dev/null differ diff --git a/apps/safe-claiming-app/src/assets/images/coins_flipped.png b/apps/safe-claiming-app/src/assets/images/coins_flipped.png deleted file mode 100644 index 9414191d5..000000000 Binary files a/apps/safe-claiming-app/src/assets/images/coins_flipped.png and /dev/null differ diff --git a/apps/safe-claiming-app/src/assets/images/delegates.svg b/apps/safe-claiming-app/src/assets/images/delegates.svg deleted file mode 100644 index e3ca92484..000000000 --- a/apps/safe-claiming-app/src/assets/images/delegates.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/apps/safe-claiming-app/src/assets/images/double-green-tile.svg b/apps/safe-claiming-app/src/assets/images/double-green-tile.svg deleted file mode 100644 index 49d469485..000000000 --- a/apps/safe-claiming-app/src/assets/images/double-green-tile.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/apps/safe-claiming-app/src/assets/images/external_link.svg b/apps/safe-claiming-app/src/assets/images/external_link.svg deleted file mode 100644 index f7a1be04c..000000000 --- a/apps/safe-claiming-app/src/assets/images/external_link.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - diff --git a/apps/safe-claiming-app/src/assets/images/illustration.svg b/apps/safe-claiming-app/src/assets/images/illustration.svg deleted file mode 100644 index 604f1b82a..000000000 --- a/apps/safe-claiming-app/src/assets/images/illustration.svg +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/apps/safe-claiming-app/src/assets/images/lock.svg b/apps/safe-claiming-app/src/assets/images/lock.svg deleted file mode 100644 index dd4088a33..000000000 --- a/apps/safe-claiming-app/src/assets/images/lock.svg +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/apps/safe-claiming-app/src/assets/images/pie-chart-educational.svg b/apps/safe-claiming-app/src/assets/images/pie-chart-educational.svg deleted file mode 100644 index efc8b3b81..000000000 --- a/apps/safe-claiming-app/src/assets/images/pie-chart-educational.svg +++ /dev/null @@ -1,19 +0,0 @@ - - - - - - - - - - - - - - - - - - - diff --git a/apps/safe-claiming-app/src/assets/images/placeholder.svg b/apps/safe-claiming-app/src/assets/images/placeholder.svg deleted file mode 100644 index ea36fda5a..000000000 --- a/apps/safe-claiming-app/src/assets/images/placeholder.svg +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/apps/safe-claiming-app/src/assets/images/safe-header-logo-gradient.svg b/apps/safe-claiming-app/src/assets/images/safe-header-logo-gradient.svg deleted file mode 100644 index 43a9d0688..000000000 --- a/apps/safe-claiming-app/src/assets/images/safe-header-logo-gradient.svg +++ /dev/null @@ -1,29 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/apps/safe-claiming-app/src/assets/images/safe-header-logo-plain.svg b/apps/safe-claiming-app/src/assets/images/safe-header-logo-plain.svg deleted file mode 100644 index c98a92368..000000000 --- a/apps/safe-claiming-app/src/assets/images/safe-header-logo-plain.svg +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - - diff --git a/apps/safe-claiming-app/src/assets/images/safe-token.svg b/apps/safe-claiming-app/src/assets/images/safe-token.svg deleted file mode 100644 index c3b045a37..000000000 --- a/apps/safe-claiming-app/src/assets/images/safe-token.svg +++ /dev/null @@ -1,17 +0,0 @@ - - - - - - - - - - - - - - - - - diff --git a/apps/safe-claiming-app/src/assets/images/safe_icon.svg b/apps/safe-claiming-app/src/assets/images/safe_icon.svg deleted file mode 100644 index 67324362d..000000000 --- a/apps/safe-claiming-app/src/assets/images/safe_icon.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/apps/safe-claiming-app/src/assets/images/sent.svg b/apps/safe-claiming-app/src/assets/images/sent.svg deleted file mode 100644 index 51dbad076..000000000 --- a/apps/safe-claiming-app/src/assets/images/sent.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/apps/safe-claiming-app/src/assets/images/single-green-tile.svg b/apps/safe-claiming-app/src/assets/images/single-green-tile.svg deleted file mode 100644 index ef7509f98..000000000 --- a/apps/safe-claiming-app/src/assets/images/single-green-tile.svg +++ /dev/null @@ -1,3 +0,0 @@ - - - diff --git a/apps/safe-claiming-app/src/assets/images/success.svg b/apps/safe-claiming-app/src/assets/images/success.svg deleted file mode 100644 index 1fd223e01..000000000 --- a/apps/safe-claiming-app/src/assets/images/success.svg +++ /dev/null @@ -1,121 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/apps/safe-claiming-app/src/assets/images/user-plus.svg b/apps/safe-claiming-app/src/assets/images/user-plus.svg deleted file mode 100644 index b34d64241..000000000 --- a/apps/safe-claiming-app/src/assets/images/user-plus.svg +++ /dev/null @@ -1,13 +0,0 @@ - - - - - - - - - - - - - diff --git a/apps/safe-claiming-app/src/components/AppSwitch.tsx b/apps/safe-claiming-app/src/components/AppSwitch.tsx deleted file mode 100644 index abd680048..000000000 --- a/apps/safe-claiming-app/src/components/AppSwitch.tsx +++ /dev/null @@ -1,38 +0,0 @@ -import App from "src/App" -import { useLightDarkTheme } from "src/hooks/useDarkMode" -import { - Box, - CircularProgress, - CssBaseline, - ThemeProvider, -} from "@mui/material" -import Widget from "src/widgets/Widget" -import SafeProvider from "@safe-global/safe-apps-react-sdk" - -export const AppSwitch = () => { - const theme = useLightDarkTheme() - const widgetId = window.location.hash.split("+")[0] - - return ( - - - palette.background.default, - }} - > - - - } - > - {widgetId === "#widget" ? : } - - - ) -} diff --git a/apps/safe-claiming-app/src/components/helpers/FloatingTiles.tsx b/apps/safe-claiming-app/src/components/helpers/FloatingTiles.tsx deleted file mode 100644 index 0104e41cd..000000000 --- a/apps/safe-claiming-app/src/components/helpers/FloatingTiles.tsx +++ /dev/null @@ -1,212 +0,0 @@ -import { Box, styled } from "@mui/material" -import { createRef, useEffect, useMemo, useState } from "react" - -// Percentage value -const radius = 40 -const minSize = 20 -const maxSize = 50 -const varianceFactor = 4 -const animationDuration = "90s" - -const Orbit = styled(Box)` - width: 1000px; - height: 1000px; - z-index: -1; - margin: auto; - opacity: 70%; - overflow: visible; - animation: 2s ease-in-out 1 grow, - ${animationDuration} linear 2s infinite orbit; - /* animation-iteration-count: 1, infinite; - /* Tone down the animation to avoid vestibular motion triggers */ - @media (prefers-reduced-motion) { - animation-name: none; - } - - @keyframes grow { - from { - transform: scale(0.05); - } - to { - transform: scale(1); - } - } - - @keyframes orbit { - 0% { - transform: rotate(0deg); - } - 50% { - transform: rotate(180deg); - } - 100% { - transform: rotate(360deg); - } - } -` - -const TileBox = styled(Box)` - animation-iteration-count: infinite; - animation-timing-function: linear; - animation-delay: 2s; - animation-name: anti-orbit; - animation-duration: ${animationDuration}; - /* Tone down the animation to avoid vestibular motion triggers */ - @media (prefers-reduced-motion) { - animation-name: none; - } - - position: absolute; - - @keyframes anti-orbit { - 0% { - transform: rotate(0deg); - } - 50% { - transform: rotate(-180deg); - } - 100% { - transform: rotate(-360deg); - } - } -` - -const ScaleWrapper = styled("div")` - position: absolute; - transition: transform 1s ease-in; - border-radius: 20%; -` - -/** - * Uses Box Muller transform to generate a 0,1 gaussian - * - */ -const randomGaussian = () => { - const u = 1 - Math.random() - const v = Math.random() - return Math.sqrt(-2.0 * Math.log(u)) * Math.cos(2.0 * Math.PI * v) -} - -const randomPointOnCircle = () => { - const angle = Math.random() * Math.PI * 2 - const adjustedRadius = radius + varianceFactor * randomGaussian() - const x = Math.cos(angle) * adjustedRadius + 50 - const y = Math.sin(angle) * adjustedRadius + 50 - return [x, y] -} - -type TileSpec = { - top: string - left: string - size: string -} - -const Tile = ({ - top, - left, - size, - visible, - startTime, - color, -}: { - top: string - left: string - size: string - visible: boolean - startTime: number | undefined | null - color: string -}) => { - const tileRef = createRef() - - useEffect(() => { - const animation = tileRef.current?.getAnimations()[0] - if (animation && startTime) { - animation.startTime = startTime - } - }, [startTime, tileRef]) - - return ( - - - - ) -} - -export const FloatingTiles = ({ - // Number between 0 and 1 - progress, - maxTiles, - color, -}: { - progress: number - maxTiles: number - color: string -}) => { - const orbitRef = createRef() - const [animationStartTime, setAnimationStartTime] = useState() - - const tileSpec: TileSpec[] = useMemo(() => { - return Array.apply("", Array(maxTiles)).map(() => { - const [x, y] = randomPointOnCircle() - - const top = `${x.toFixed(2)}%` - const left = `${y.toFixed(2)}%` - - const size = `${(Math.random() * (maxSize - minSize) + minSize).toFixed( - 2 - )}px` - - return { - top, - left, - size, - } - }) - }, [maxTiles]) - - useEffect(() => { - setTimeout(() => { - if (!animationStartTime) { - setAnimationStartTime(orbitRef.current?.getAnimations()[0]?.startTime) - } - }, 0) - }, [animationStartTime, orbitRef]) - - const visibleTiles = useMemo(() => { - const minTiles = Math.floor(maxTiles / 5) - const progressTiles = maxTiles - minTiles - return minTiles + Math.floor(progress * progressTiles) - }, [progress, maxTiles]) - - return ( - palette.background.default, - }} - > - - {tileSpec.map((spec, idx) => ( - - ))} - - - ) -} diff --git a/apps/safe-claiming-app/src/components/helpers/Loading.tsx b/apps/safe-claiming-app/src/components/helpers/Loading.tsx deleted file mode 100644 index d117c7262..000000000 --- a/apps/safe-claiming-app/src/components/helpers/Loading.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { CircularProgress, Paper, Typography } from "@mui/material" - -export const Loading = () => { - return ( - - - Loading airdrop data for connected Safe - - - ) -} diff --git a/apps/safe-claiming-app/src/components/helpers/NavButtons.tsx b/apps/safe-claiming-app/src/components/helpers/NavButtons.tsx deleted file mode 100644 index 5a5b47431..000000000 --- a/apps/safe-claiming-app/src/components/helpers/NavButtons.tsx +++ /dev/null @@ -1,65 +0,0 @@ -import { Box, Button } from "@mui/material" -import ArrowBackIcon from "@mui/icons-material/ArrowBack" -import { useDarkMode } from "src/hooks/useDarkMode" - -export const NavButtons = ({ - handleBack, - handleNext, - isNextEnabled = true, - nextLabel, - finalScreen, -}: { - handleBack?: () => void - handleNext?: () => void - isNextEnabled?: boolean - nextLabel?: string - finalScreen?: boolean -}) => { - const showNext = handleNext !== undefined - const showBack = handleBack !== undefined - nextLabel = nextLabel || "Next" - const isDarkMode = useDarkMode() - - return ( - - {showBack && ( - - )} - {showNext && ( - - )} - - ) -} diff --git a/apps/safe-claiming-app/src/components/helpers/ProgressBar.tsx b/apps/safe-claiming-app/src/components/helpers/ProgressBar.tsx deleted file mode 100644 index 4c95e808c..000000000 --- a/apps/safe-claiming-app/src/components/helpers/ProgressBar.tsx +++ /dev/null @@ -1,39 +0,0 @@ -import { Box, styled } from "@mui/material" - -const StyledBar = styled(Box)` - position: relative; - max-width: 100%; - overflow-y: auto; - height: 6px; - border-top-left-radius: 4px; - border-top-right-radius: 4px; - border-bottom-right-radius: 4px; - margin-bottom: -6px; - - transition: width 0.5s ease; - - // Animation: - background: linear-gradient( - 90deg, - ${({ theme }) => theme.palette.safeGreen.main}, - #5fddff - ); - background-size: 300% 300%; - animation: gradient 10s ease infinite; - - @keyframes gradient { - 0% { - background-position: 0% 0%; - } - 50% { - background-position: 100% 0%; - } - 100% { - background-position: 0% 0%; - } - } -` - -export const ProgressBar = ({ progress }: { progress: number }) => { - return -} diff --git a/apps/safe-claiming-app/src/components/helpers/ProgressCircle.tsx b/apps/safe-claiming-app/src/components/helpers/ProgressCircle.tsx deleted file mode 100644 index c8e44f871..000000000 --- a/apps/safe-claiming-app/src/components/helpers/ProgressCircle.tsx +++ /dev/null @@ -1,37 +0,0 @@ -import Box from "@mui/material/Box" -import CircularProgress, { - circularProgressClasses, - CircularProgressProps, -} from "@mui/material/CircularProgress" - -export const ProgressCircle = (props: CircularProgressProps) => { - return ( - - theme.palette.grey[200], - }} - size={40} - thickness={3} - {...props} - value={100} - /> - - - ) -} diff --git a/apps/safe-claiming-app/src/components/helpers/ScrollContext.tsx b/apps/safe-claiming-app/src/components/helpers/ScrollContext.tsx deleted file mode 100644 index 10e670621..000000000 --- a/apps/safe-claiming-app/src/components/helpers/ScrollContext.tsx +++ /dev/null @@ -1,61 +0,0 @@ -import styled from "@emotion/styled" -import { Box } from "@mui/material" -import { createContext, useContext, useRef, useState } from "react" - -type ScrollContextType = { - storeScrollPosition: () => void - restoreScrollPosition: () => void -} - -const ScrollContext = createContext({ - storeScrollPosition() { - // empty default - }, - restoreScrollPosition() { - // empty default - }, -}) - -const Wrapper = styled(Box)` - background: transparent; - position: fixed; - left: 0; - top: 0; - width: 100vw; - height: 100vh; - align-items: center; - overflow: auto; - display: flex; - justify-content: center; -` - -export const ScrollContextProvider = ({ - children, -}: { - children: JSX.Element -}) => { - const scrollWrapperRef = useRef() - const [storedScrollPosition, setStoredScrollPosition] = useState() - const storeScrollPosition = () => - setStoredScrollPosition(scrollWrapperRef.current?.scrollTop) - - const restoreScrollPosition = () => - setTimeout( - () => - storedScrollPosition && - scrollWrapperRef.current?.scrollTo(0, storedScrollPosition) - ) - - return ( - - {children} - - ) -} - -export const useScrollContext = () => useContext(ScrollContext) diff --git a/apps/safe-claiming-app/src/components/helpers/UnexpectedError.tsx b/apps/safe-claiming-app/src/components/helpers/UnexpectedError.tsx deleted file mode 100644 index c0ec05393..000000000 --- a/apps/safe-claiming-app/src/components/helpers/UnexpectedError.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { Paper, Typography } from "@mui/material" - -export const UnexpectedError = ({ error }: { error: string | Error }) => { - return ( - - palette.error.main, - }} - > - Unexpected error while loading the app:{" "} - {typeof error === "string" ? error : error.message} - - - Please try refreshing the app. - - - ) -} diff --git a/apps/safe-claiming-app/src/components/helpers/UnsupportedNetwork.tsx b/apps/safe-claiming-app/src/components/helpers/UnsupportedNetwork.tsx deleted file mode 100644 index f7bb46f57..000000000 --- a/apps/safe-claiming-app/src/components/helpers/UnsupportedNetwork.tsx +++ /dev/null @@ -1,22 +0,0 @@ -import { Paper, Typography } from "@mui/material" - -export const UnsupportedNetwork = () => { - return ( - - - Only Mainnet, Rinkeby and Goerli are supported by this Safe App - - - ) -} diff --git a/apps/safe-claiming-app/src/components/steps/Claim/ClaimCard.tsx b/apps/safe-claiming-app/src/components/steps/Claim/ClaimCard.tsx deleted file mode 100644 index aba9a3fa6..000000000 --- a/apps/safe-claiming-app/src/components/steps/Claim/ClaimCard.tsx +++ /dev/null @@ -1,180 +0,0 @@ -import { ShieldOutlined } from "@mui/icons-material" -import { - Grid, - Paper, - Typography, - Tooltip, - Badge, - styled, - useTheme, -} from "@mui/material" -import { ethers } from "ethers" -import { formatAmount } from "src/utils/format" -import { Odometer } from "./Odometer/Odometer" -import { ReactComponent as SingleGreenTile } from "src/assets/images/single-green-tile.svg" -import { ReactComponent as DoubleGreenTile } from "src/assets/images/double-green-tile.svg" -import InfoOutlined from "@mui/icons-material/InfoOutlined" -import { useDarkMode } from "src/hooks/useDarkMode" - -const StyledSingleTile = styled(SingleGreenTile)` - position: absolute; - top: 0px; - right: 80px; -` - -const StyledDoubleTile = styled(DoubleGreenTile)` - position: absolute; - bottom: 15px; - right: 0px; -` -const StyledBadge = styled(Badge)<{ dotColor: string }>` - & .MuiBadge-badge { - right: 4px; - bottom: 5px; - height: 6px; - min-width: 6px; - background-color: ${(props) => props.dotColor}; - } -` - -const AmountDisplay = ({ amount }: { amount: string }) => { - const amountInDecimal = ethers.utils.formatEther(amount) - return ( - - SAFE - - ) -} - -export const ClaimCard = ({ - isGuardian, - ecosystemAmount, - totalAmount, - variant, -}: { - isGuardian: boolean - ecosystemAmount: string - totalAmount: string - variant: "claimable" | "vesting" -}) => { - const { palette } = useTheme() - const ecosystemAmountInDecimals = formatAmount( - Number(ethers.utils.formatEther(ecosystemAmount)), - 2 - ) - - const isDarkMode = useDarkMode() - - const isClaimable = variant === "claimable" - - const backgroundColor = isClaimable - ? palette.primary.main - : palette.background.default - - const color = isClaimable ? palette.background.default : palette.text.primary - - const dotColor = isClaimable ? "white" : "#12ff80" - - return ( - - - {isClaimable && ( - - )} - {isClaimable && ( - - )} - - - {isClaimable ? "Claim now" : "Claim in future (vesting)"} - {!isClaimable && ( - - Linear vesting over 4 years from a starting date of 27.09.2022 - - } - arrow - placement="top" - > - palette.secondary.main, - }} - /> - - )} - - - - - Total - - - {isGuardian ? ( - <> - This includes a Safe Guardian allocation of - {ecosystemAmountInDecimals} SAFE - - ) : ( - "Not eligible for Safe Guardian allocation. Contribute to the community to become a Safe Guardian." - )} - - } - arrow - placement="top" - > - - - - - - - - - - - - ) -} diff --git a/apps/safe-claiming-app/src/components/steps/Claim/Odometer/Odometer.tsx b/apps/safe-claiming-app/src/components/steps/Claim/Odometer/Odometer.tsx deleted file mode 100644 index 934480b87..000000000 --- a/apps/safe-claiming-app/src/components/steps/Claim/Odometer/Odometer.tsx +++ /dev/null @@ -1,131 +0,0 @@ -import { useEffect, useRef, useState } from "react" - -import BezierEasing from "bezier-easing" -import { getNewPosition, getStartPosition, getOffset } from "./numberPosition" -import { formatAmount } from "src/utils/format" - -const DURATION = 1000 -const NUMS = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9] - -const easeInOut = BezierEasing(0.42, 0, 0.58, 1) - -const roundNumber = (num: number, digits: number) => { - const decimal = Math.pow(10, digits) - return Math.round(num * decimal) / decimal -} - -export const Odometer = ({ - value = 0, - decimals = 2, -}: { - value: number - decimals: number -}) => { - const [start, setStart] = useState(0) - const [target, setTarget] = useState(0) - - useEffect(() => { - setStart(target) - setTarget(value) - // eslint-disable-next-line react-hooks/exhaustive-deps - }, [value]) - - const getDirection = () => target - start - - const renderNumbers = () => { - const direction = getDirection() - const valueString = formatAmount(value, decimals) - const targetString = formatAmount(target, decimals).padStart( - valueString.length, - "0" - ) - return targetString.split("").map((digit, idx) => { - const targetDigit = digit || 0 - if (targetDigit === ".") { - return . - } - if (targetDigit === ",") { - return , - } - return ( - - ) - }) - } - - return ( -
- {renderNumbers()} -
- ) -} - -const RollingNumber = ({ - direction, - target, -}: { - direction: number - target: number -}) => { - const targetRef = useRef(1) - - const [currentPosition, setCurrentPosition] = useState(getNewPosition(target)) - - useEffect(() => { - if (targetRef.current === target) { - // no new target. - return - } - targetRef.current = target - const startTime = new Date().getTime() - const startPosition = roundNumber( - getStartPosition(currentPosition, direction), - 3 - ) - const offset = getOffset(startPosition, target, direction) - - function tick() { - const elapsed = new Date().getTime() - startTime - // If the browser window is in the background / minimized it will optimize and not call the requestAnimationFrame until in foreground causing wrong numbers for progress. - const progress = Math.min(elapsed / DURATION, 1) - const easedProgress = easeInOut(progress) - const position = startPosition + easedProgress * offset - setCurrentPosition(roundNumber(position, 3)) - - if (elapsed < DURATION && targetRef.current === target) { - requestAnimationFrame(tick) - } - } - - tick() - }, [direction, target, currentPosition]) - - function renderDigits() { - return NUMS.map((digit, idx) => {digit}) - } - - return ( -
-
-
- {renderDigits()} -
-
- ) -} diff --git a/apps/safe-claiming-app/src/components/steps/Claim/Odometer/__tests__/numberPosition.test.ts b/apps/safe-claiming-app/src/components/steps/Claim/Odometer/__tests__/numberPosition.test.ts deleted file mode 100644 index 69316663d..000000000 --- a/apps/safe-claiming-app/src/components/steps/Claim/Odometer/__tests__/numberPosition.test.ts +++ /dev/null @@ -1,45 +0,0 @@ -import { getNewPosition, getOffset, getStartPosition } from "../numberPosition" - -describe("numberPositions for Odometer", () => { - it("getNewPosition should multiple number by -2 (em)", () => { - expect(getNewPosition(5)).toEqual(-10) - expect(getNewPosition(0)).toEqual(-0) - expect(getNewPosition(9)).toEqual(-18) - }) - - describe("getStartPosition", () => { - it("currentPosition is in top half and direction is increasing", () => { - expect(getStartPosition(-4, 1)).toEqual(-4) - }) - - it("currentPosition is in top half and direction is decreasing", () => { - expect(getStartPosition(-4, -1)).toEqual(-24) - }) - - it("currentPosition is in bottom half and direction is increasing", () => { - expect(getStartPosition(-28, 1)).toEqual(-8) - }) - - it("currentPosition is in bottom half and direction is decreasing", () => { - expect(getStartPosition(-28, -1)).toEqual(-28) - }) - }) - - describe("getOffset", () => { - it("start is in top half, direction is increasing and next target is in top half", () => { - expect(getOffset(-4, 8, 1)).toEqual(-12) - }) - - it("start is in top half, direction is increasing and next target is in bottom half", () => { - expect(getOffset(-10, 4, 1)).toEqual(-18) - }) - - it("start is in bottom half, direction is decreasing and next target is in bottom half", () => { - expect(getOffset(-38, 1, -1)).toEqual(16) - }) - - it("start is in bottom half, direction is decreasing and next target is in top half", () => { - expect(getOffset(-24, 5, -1)).toEqual(14) - }) - }) -}) diff --git a/apps/safe-claiming-app/src/components/steps/Claim/Odometer/numberPosition.ts b/apps/safe-claiming-app/src/components/steps/Claim/Odometer/numberPosition.ts deleted file mode 100644 index b7abc7fba..000000000 --- a/apps/safe-claiming-app/src/components/steps/Claim/Odometer/numberPosition.ts +++ /dev/null @@ -1,17 +0,0 @@ -const FULL_LENGTH = -20 - -/** - * Moves the start position to the correct half. - */ -export const getStartPosition = (currentPosition: number, direction: number) => - (currentPosition % FULL_LENGTH) + (direction >= 0 ? 0 : FULL_LENGTH) - -export const getOffset = (start: number, target: number, direction: number) => { - const newPosition = getNewPosition(target) - const diff = (newPosition - start) % FULL_LENGTH - return diff > 0 && direction > 0 ? diff + FULL_LENGTH : diff -} - -export const getNewPosition = (target: number) => { - return target * -2 -} diff --git a/apps/safe-claiming-app/src/components/steps/Claim/SelectedDelegate.tsx b/apps/safe-claiming-app/src/components/steps/Claim/SelectedDelegate.tsx deleted file mode 100644 index fd1ce8430..000000000 --- a/apps/safe-claiming-app/src/components/steps/Claim/SelectedDelegate.tsx +++ /dev/null @@ -1,58 +0,0 @@ -import { Card, CardHeader, IconButton } from "@mui/material" -import { useState } from "react" - -import CheckSharpIcon from "@mui/icons-material/CheckSharp" -import ModeEditOutlinedIcon from "@mui/icons-material/ModeEditOutlined" -import { DelegateEntry } from "src/hooks/useDelegatesFile" -import { DelegateAvatar } from "../Delegate/DelegateAvatar" -import { shortenAddress } from "src/utils/format" - -export const SelectedDelegate = ({ - onClick, - delegate, -}: { - onClick?: () => void - delegate: DelegateEntry -}) => { - const [isHover, setIsHover] = useState(false) - const hasAction = onClick !== undefined - - return ( - - } - title={delegate.name} - subheader={delegate.ens ?? shortenAddress(delegate.address)} - sx={{ - "& .MuiCardHeader-action": { - margin: "0px", - }, - }} - action={ - hasAction && ( - - isHover - ? `${palette.safeGreen.light} !important` - : `${palette.border.light} !important`, - border: ({ palette }) => - isHover - ? `1px solid ${palette.safeGreen.main}` - : `1px solid ${palette.text.secondary}`, - }} - onMouseEnter={() => setIsHover(true)} - onMouseLeave={() => setIsHover(false)} - onClick={onClick} - > - {isHover ? : } - - ) - } - /> - - ) -} diff --git a/apps/safe-claiming-app/src/components/steps/Claim/index.tsx b/apps/safe-claiming-app/src/components/steps/Claim/index.tsx deleted file mode 100644 index 490de9702..000000000 --- a/apps/safe-claiming-app/src/components/steps/Claim/index.tsx +++ /dev/null @@ -1,309 +0,0 @@ -import { - Box, - Button, - Divider, - Grid, - InputAdornment, - Paper, - styled, - TextField, - Typography, -} from "@mui/material" -import { ChangeEvent, useState } from "react" -import { AppState } from "src/App" -import { ReactComponent as SafeIcon } from "src/assets/images/safe-token.svg" - -import { SelectedDelegate } from "src/components/steps/Claim/SelectedDelegate" -import { maxDecimals, minMaxValue, mustBeFloat } from "src/utils/validation" - -import { useSafeAppsSDK } from "@safe-global/safe-apps-react-sdk" -import { BigNumber, ethers } from "ethers" -import { useAmounts } from "src/hooks/useAmounts" -import { InfoOutlined } from "@mui/icons-material" -import { ClaimCard } from "./ClaimCard" -import { formatAmount } from "src/utils/format" -import { NavButtons } from "src/components/helpers/NavButtons" -import { createClaimAndDelegateTxs } from "src/utils/contracts/createClaimAndDelegateTxs" - -const ButtonLink = styled("button")` - border: 0; - background: none; - color: ${({ theme }) => theme.palette.primary.main}; - padding: 0; - font-size: 16px; - font-weight: bold; - cursor: pointer; -` - -type Props = { - handleBack: () => void - handleNext: () => void - state: AppState - handleUpdateState: (newState: AppState) => void -} - -const validateAmount = (amount: string, maxAmount: string) => { - return ( - mustBeFloat(amount) ?? - minMaxValue(0, maxAmount, amount) ?? - maxDecimals(amount, 18) - ) -} - -const Claim = ({ handleBack, state, handleUpdateState, handleNext }: Props) => { - const { sdk, safe } = useSafeAppsSDK() - - const { delegate, vestingData, isTokenPaused } = state - const [amount, setAmount] = useState() - const [isMaxAmountSelected, setIsMaxAmountSelected] = useState(false) - const [amountError, setAmountError] = useState() - - const userClaim = - vestingData.find((vesting) => vesting.tag === "user") ?? null - const ecosystemClaim = - vestingData.find((vesting) => vesting.tag === "ecosystem") ?? null - const investorClaim = - vestingData.find((vesting) => vesting.tag === "investor") ?? null - - const [userAirdropClaimable, userAirdropInVesting] = useAmounts(userClaim) - const [ecosystemClaimable, ecosystemInVesting] = useAmounts(ecosystemClaim) - const [investorClaimable, investorInVesting] = useAmounts(investorClaim) - - const totalAmountClaimable = BigNumber.from(userAirdropClaimable) - .add(BigNumber.from(ecosystemClaimable)) - .add(BigNumber.from(investorClaimable)) - .toString() - const totalAmountInVesting = BigNumber.from(userAirdropInVesting) - .add(BigNumber.from(ecosystemInVesting)) - .add(BigNumber.from(investorInVesting)) - .toString() - - const totalAllocation = BigNumber.from(userClaim?.amount || "0") - .add(ecosystemClaim?.amount || "0") - .add(investorClaim?.amount || "0") - .toString() - - const hasDelegateChanged = - state.delegate !== undefined && - state.delegateAddressFromContract !== state.delegate.address - - const investorClaimingDisabled = investorClaim !== null && isTokenPaused - - const isAmountGTZero = amount && !amountError && Number.parseFloat(amount) > 0 - - const buttonDisabled = - !(isAmountGTZero || hasDelegateChanged) || - (investorClaimingDisabled && isAmountGTZero) || - !!amountError - - const handleAmountChange = (event: ChangeEvent) => { - const error = validateAmount( - event.target.value || "0", - ethers.utils.formatEther(totalAmountClaimable) - ) - setAmountError(error) - setAmount(event.target.value) - } - - const setToMaxAmount = () => { - const amountAsNumber = Number( - ethers.utils.formatEther(totalAmountClaimable) - ) - setAmount(amountAsNumber.toFixed(2)) - setIsMaxAmountSelected(true) - setAmountError(undefined) - } - - const claimTokens = async () => { - const txs = createClaimAndDelegateTxs({ - appState: state, - amount: amount || "0", - chainId: safe.chainId, - safeAddress: safe.safeAddress, - investorClaimable, - userClaimable: userAirdropClaimable, - isMaxAmountSelected, - }) - - try { - await sdk.txs.send({ txs }) - - handleUpdateState({ - ...state, - claimedAmount: amount, - }) - handleNext() - } catch (error) { - console.error(error) - } - } - - return ( - - - - Your SAFE allocation: - - - - - - - - palette.secondary.main, - }} - /> - - Awarded total allocation is{" "} - - {formatAmount(Number(ethers.utils.formatEther(totalAllocation)), 2)}{" "} - SAFE - - - - - - - - - - <> - - How much do you want to claim? - - - Select all tokens or define a custom amount. - - - - ) => { - handleAmountChange(event) - setIsMaxAmountSelected(false) - }} - InputProps={{ - startAdornment: ( - - - - ), - endAdornment: ( - - Max - - ), - }} - sx={{ - "& .MuiInputBase-input": { - padding: "12px 14px 12px 0", - fontWeight: 700, - }, - }} - /> - - - - - - {!investorClaim && ( - - palette.secondary.main, - }} - /> - - Execute at least one claim of any amount of tokens until - 27.12.22 10:00 CET or your allocation will be transferred back - to the SafeDAO treasury - - - )} - {investorClaim && isTokenPaused && ( - - palette.secondary.main, - }} - /> - - Claiming will be available once the token is transferable - - - )} - - - {delegate && ( - - - Delegating to - - - - palette.secondary.main, - }} - /> - - You only delegate your voting power and not the ownership over - your tokens. - - - - )} - - - - ) -} - -export default Claim diff --git a/apps/safe-claiming-app/src/components/steps/Delegate/DelegateAvatar.tsx b/apps/safe-claiming-app/src/components/steps/Delegate/DelegateAvatar.tsx deleted file mode 100644 index b1f26d79d..000000000 --- a/apps/safe-claiming-app/src/components/steps/Delegate/DelegateAvatar.tsx +++ /dev/null @@ -1,36 +0,0 @@ -import { Avatar } from "@mui/material" -import { DelegateEntry } from "src/hooks/useDelegatesFile" -import CheckCircleRoundedIcon from "@mui/icons-material/CheckCircleRounded" -import { GUARDIANS_IMAGE_URL } from "src/config/constants" - -export const DelegateAvatar = ({ - delegate, - selected, -}: { - delegate: DelegateEntry - selected: boolean -}) => { - return ( - - - {selected && ( - palette.background.default, - borderRadius: "10px", - }} - /> - )} - - ) -} diff --git a/apps/safe-claiming-app/src/components/steps/Delegate/DelegateCard.tsx b/apps/safe-claiming-app/src/components/steps/Delegate/DelegateCard.tsx deleted file mode 100644 index 5bf9d114f..000000000 --- a/apps/safe-claiming-app/src/components/steps/Delegate/DelegateCard.tsx +++ /dev/null @@ -1,56 +0,0 @@ -import { - Card, - CardActionArea, - CardContent, - CardHeader, - Typography, -} from "@mui/material" - -import { DelegateEntry } from "src/hooks/useDelegatesFile" -import { shortenAddress } from "src/utils/format" -import { DelegateAvatar } from "./DelegateAvatar" - -export const DelegateCard = ({ - onClick, - delegate, - selected, -}: { - onClick: (delegate: DelegateEntry) => void - delegate: DelegateEntry - selected: boolean -}) => { - return ( - ({ - borderColor: selected ? palette.primary.main : "#EEEFF0", - })} - > - onClick(delegate)}> - } - title={delegate.name} - subheader={delegate.ens || shortenAddress(delegate.address)} - sx={{ padding: 0, margin: 2 }} - /> - - - {delegate.reason} - - - - - ) -} diff --git a/apps/safe-claiming-app/src/components/steps/Delegate/DelegateList.tsx b/apps/safe-claiming-app/src/components/steps/Delegate/DelegateList.tsx deleted file mode 100644 index d5f37afd9..000000000 --- a/apps/safe-claiming-app/src/components/steps/Delegate/DelegateList.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import { - Button, - Grid, - TextField, - Typography, - InputAdornment, -} from "@mui/material" -import SearchIcon from "@mui/icons-material/Search" -import React, { useMemo, useState } from "react" -import { DelegateCard } from "./DelegateCard" -import { DelegateEntry } from "src/hooks/useDelegatesFile" - -const filterDelegates = (searchTerm: string, delegateData: DelegateEntry[]) => { - const lowerCaseSearchTerm = searchTerm.toLowerCase() - return delegateData.filter((delegate) => - [delegate.address, delegate.ens, delegate.name].some((value) => - value - ?.toLowerCase() - .split(" ") - .some((splitValue) => splitValue.startsWith(lowerCaseSearchTerm)) - ) - ) -} - -export const DelegateList = ({ - chosenDelegate, - onCardClick, - delegateData, - showAll, - setShowAll, -}: { - chosenDelegate?: DelegateEntry - onCardClick: (delegate: DelegateEntry) => void - delegateData: DelegateEntry[] - showAll: boolean - setShowAll: React.Dispatch> -}) => { - const [delegateSearchTerm, setDelegateSearchTerm] = useState("") - const filteredDelegates = useMemo(() => { - let currentDelegates = - delegateSearchTerm.length > 0 - ? filterDelegates(delegateSearchTerm, delegateData) - : delegateData - - if (chosenDelegate) { - currentDelegates = [ - chosenDelegate, - ...currentDelegates.filter( - (delegate) => delegate.address !== chosenDelegate.address - ), - ] - } - - if (!showAll) { - currentDelegates = currentDelegates.slice( - 0, - Math.min(currentDelegates.length, 6) - ) - } - return currentDelegates - }, [chosenDelegate, delegateData, delegateSearchTerm, showAll]) - - return ( - <> - - - - setDelegateSearchTerm(event.currentTarget.value) - } - variant="outlined" - value={delegateSearchTerm} - placeholder="Search name, address or ENS" - InputProps={{ - startAdornment: ( - - theme.palette.primary.light }} - /> - - ), - }} - /> - - - - {delegateData.length} available delegates - - - - - {filteredDelegates.map((delegateCandidate) => ( - - - - ))} - - - - ) -} diff --git a/apps/safe-claiming-app/src/components/steps/Delegate/DelegateSwitch.tsx b/apps/safe-claiming-app/src/components/steps/Delegate/DelegateSwitch.tsx deleted file mode 100644 index b4369108c..000000000 --- a/apps/safe-claiming-app/src/components/steps/Delegate/DelegateSwitch.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import CheckCircleRoundedIcon from "@mui/icons-material/CheckCircleRounded" -import { - Card, - CardActionArea, - CardContent, - Grid, - Typography, -} from "@mui/material" -import { ReactComponent as DelegatesIcon } from "src/assets/images/delegates.svg" -import { ReactComponent as CustomAddressIcon } from "src/assets/images/user-plus.svg" - -export const DelegateSwitch = ({ - isCustomDelegation, - setIsCustomDelegation, -}: { - isCustomDelegation: boolean - setIsCustomDelegation: (isCustom: boolean) => void -}) => { - return ( - - - {!isCustomDelegation && ( - palette.background.paper, - }} - /> - )} - palette.primary.main } - : undefined - } - > - setIsCustomDelegation(false)}> - -
- - Delegate to a Safe Guardian -
-
-
-
-
- - {isCustomDelegation && ( - palette.background.paper, - }} - /> - )} - palette.primary.main } - : undefined - } - > - setIsCustomDelegation(true)}> - -
- - Custom address -
-
-
-
-
-
- ) -} diff --git a/apps/safe-claiming-app/src/components/steps/Delegate/ExpandedDelegateCard.tsx b/apps/safe-claiming-app/src/components/steps/Delegate/ExpandedDelegateCard.tsx deleted file mode 100644 index 6ba16f036..000000000 --- a/apps/safe-claiming-app/src/components/steps/Delegate/ExpandedDelegateCard.tsx +++ /dev/null @@ -1,85 +0,0 @@ -import CloseIcon from "@mui/icons-material/Close" -import { - Card, - CardContent, - CardHeader, - IconButton, - Link, - Typography, -} from "@mui/material" -import { NavButtons } from "src/components/helpers/NavButtons" -import { DelegateEntry } from "src/hooks/useDelegatesFile" -import { getExplorerURL, shortenAddress } from "src/utils/format" -import { DelegateAvatar } from "./DelegateAvatar" -import { ReactComponent as ExternalLink } from "src/assets/images/external_link.svg" -import { useSafeAppsSDK } from "@safe-global/safe-apps-react-sdk" - -export const ExpandedDelegateCard = ({ - onClick, - delegate, - selected, - onClose, -}: { - onClick: (delegate: DelegateEntry) => void - delegate: DelegateEntry - selected: boolean - onClose?: () => void -}) => { - const { safe } = useSafeAppsSDK() - - return ( - ({ - borderColor: selected ? palette.primary.main : "#EEEFF0", - })} - > - } - action={ - - - - } - title={delegate.name} - subheader={ - <> - - {delegate.ens || shortenAddress(delegate.address)} - - - - - - } - sx={{ padding: 0, margin: 2 }} - /> - - - What are your reasons for wanting to be a delegate? - - {delegate.reason} - {delegate.contribution && ( - <> - - As a founding Guardian, what was your previous contribution? - - - {delegate.contribution} - - - )} - - onClick(delegate)} - nextLabel="Select as delegate" - /> - - - ) -} diff --git a/apps/safe-claiming-app/src/components/steps/Delegate/index.tsx b/apps/safe-claiming-app/src/components/steps/Delegate/index.tsx deleted file mode 100644 index 5ad4c2155..000000000 --- a/apps/safe-claiming-app/src/components/steps/Delegate/index.tsx +++ /dev/null @@ -1,241 +0,0 @@ -import CheckCircleRoundedIcon from "@mui/icons-material/CheckCircleRounded" -import InfoOutlined from "@mui/icons-material/InfoOutlined" -import { - Box, - CircularProgress, - Grid, - InputAdornment, - Paper, - TextField, - Typography, -} from "@mui/material" -import React, { useState } from "react" -import { useEnsResolution } from "src/hooks/useEnsResolution" - -import { AppState } from "src/App" -import { DelegateList } from "./DelegateList" -import { DelegateSwitch } from "./DelegateSwitch" -import { ExpandedDelegateCard } from "./ExpandedDelegateCard" -import { DelegateEntry } from "src/hooks/useDelegatesFile" -import { NavButtons } from "src/components/helpers/NavButtons" -import { useScrollContext } from "src/components/helpers/ScrollContext" -import SearchIcon from "@mui/icons-material/Search" - -const Delegate = ({ - handleNext, - handleBack, - handleUpdateState, - state, -}: { - handleNext: () => void - handleBack: () => void - handleUpdateState: (newState: AppState) => void - state: AppState -}) => { - const { delegate, delegateData } = state - const delegateAddress = delegate?.address - const delegateENSName = delegate?.ens - const chosenDelegateFromList = delegateData.find( - (delegateFromList) => delegateFromList.address === delegateAddress - ) - const isCustomDelegate = Boolean(delegateAddress) && !chosenDelegateFromList - - const [customAddress, setCustomAddress] = useState( - isCustomDelegate ? delegateENSName ?? delegateAddress ?? "" : "" - ) - const [chosenDelegate, setChosenDelegate] = useState< - DelegateEntry | undefined - >(chosenDelegateFromList) - const [expandedDelegate, setExpandedDelegate] = useState< - DelegateEntry | undefined - >(undefined) - const [isCustomDelegation, setIsCustomDelegation] = useState(isCustomDelegate) - - const [showAll, setShowAll] = useState(false) - - const scrollContext = useScrollContext() - - const [customEnsResult, customAddressError, customEnsLoading] = - useEnsResolution(customAddress) - - const isValidCustomAddressSet = isCustomDelegation && Boolean(customEnsResult) - - const handleBackButton = () => { - if (expandedDelegate) { - setExpandedDelegate(undefined) - } else { - handleBack() - } - } - - // If we toggle to custom delegation we have to update the state with the custom ens resolved address - const submitDelegate = () => { - if (isCustomDelegation && customEnsResult) { - handleUpdateState({ - ...state, - delegate: customEnsResult, - }) - } else { - handleUpdateState({ - ...state, - delegate: chosenDelegate, - }) - } - - handleNext() - } - - const onCardSelect = (chosenDelegate: DelegateEntry) => { - setChosenDelegate(chosenDelegate) - closeExpandedDelegateCard(false) - setShowAll(false) - } - - const onCardClick = (clickedDelegate: DelegateEntry) => { - // Store scroll position - scrollContext.storeScrollPosition() - setExpandedDelegate(clickedDelegate) - } - - const closeExpandedDelegateCard = (restoreScrollPosition: boolean) => { - setExpandedDelegate(undefined) - restoreScrollPosition && scrollContext.restoreScrollPosition() - } - - const enableNextButton = - (isCustomDelegation && - Boolean(customEnsResult) && - !Boolean(customAddressError)) || - (!isCustomDelegation && Boolean(chosenDelegate)) - - return ( - - <> - - Choose a delegate - - - A delegate is someone you select to make governance decisions on your - behalf. You still retain full ownership of your tokens, but your - delegate will wield the voting power associated with those tokens, - including your unvested allocation. - - {expandedDelegate ? ( -
- closeExpandedDelegateCard(true)} - selected={chosenDelegate?.address === expandedDelegate?.address} - /> -
- ) : ( - <> - - {isCustomDelegation && ( - - - - The wallet address can belong to any person but you cannot - delegate to your own Safe. - - - setCustomAddress(event.currentTarget.value) - } - variant="outlined" - value={customAddress} - error={Boolean(customAddressError)} - sx={{ - "& .MuiFormHelperText-root": { - margin: 0, - }, - }} - helperText={ - customAddressError || ( - - palette.secondary.main, - }} - /> - - For gasless voting, we suggest selecting an EOA - wallet e.g. your connected wallet. - - - ) - } - placeholder="Search name, address or ENS" - InputProps={{ - endAdornment: customEnsLoading ? ( - - ) : isValidCustomAddressSet ? ( - - ) : null, - startAdornment: ( - - theme.palette.primary.light, - }} - /> - - ), - }} - /> - - - - )} - {!isCustomDelegation && ( - - )} - - - - )} - -
- ) -} - -export default Delegate diff --git a/apps/safe-claiming-app/src/components/steps/Disclaimer/index.tsx b/apps/safe-claiming-app/src/components/steps/Disclaimer/index.tsx deleted file mode 100644 index c69b44988..000000000 --- a/apps/safe-claiming-app/src/components/steps/Disclaimer/index.tsx +++ /dev/null @@ -1,54 +0,0 @@ -import { Paper, Typography } from "@mui/material" -import Box from "@mui/system/Box" -import { NavButtons } from "src/components/helpers/NavButtons" - -type Props = { - handleBack: () => void - handleNext: () => void -} - -const Disclaimer = ({ handleNext, handleBack }: Props) => { - return ( - - - - Legal Disclaimer - - - - This App is for our community to encourage Safe ecosystem contributors - and users to unlock SafeDAO governance.

- THIS APP IS PROVIDED “AS IS” AND “AS AVAILABLE,” AT YOUR OWN RISK, AND - WITHOUT WARRANTIES OF ANY KIND. We will not be liable for any loss, - whether such loss is direct, indirect, special or consequential, - suffered by any party as a result of their use of this app.
-
- By accessing this app, you represent and warrant -
- that you are of legal age and that you will comply with any laws - applicable to you and not engage in any illegal activities; -
- that you are claiming Safe tokens to participate in the SafeDAO - governance process and that they do not represent consideration for past - or future services; -
- that you, the country you are a resident of and your wallet - address is not on any sanctions lists maintained by the United Nations, - Switzerland, the EU, UK or the US; -
- that you are responsible for any tax obligations arising out of - the interaction with this app.
-
None of the information available on this app, or made otherwise - available to you in relation to its use, constitutes any legal, tax, - financial or other advice. Where in doubt as to the action you should - take, please consult your own legal, financial, tax or other - professional advisors. -
- - -
- ) -} - -export default Disclaimer diff --git a/apps/safe-claiming-app/src/components/steps/Distribution/index.tsx b/apps/safe-claiming-app/src/components/steps/Distribution/index.tsx deleted file mode 100644 index e7826ff6c..000000000 --- a/apps/safe-claiming-app/src/components/steps/Distribution/index.tsx +++ /dev/null @@ -1,153 +0,0 @@ -import { Grid, Link, Paper, styled, Typography } from "@mui/material" -import Box from "@mui/system/Box" - -import { ReactComponent as PieChart } from "src/assets/images/pie-chart-educational.svg" -import { NavButtons } from "src/components/helpers/NavButtons" -import { FULL_PROPOSAL_URL } from "src/config/constants" -import { ReactComponent as ExternalLink } from "src/assets/images/external_link.svg" -import lightPalette from "src/config/colors" - -const PercentageWrapper = styled(Typography)` - border-radius: 6px; - padding: 0px 8px; -` - -type Props = { - handleBack: () => void - handleNext: () => void -} - -const Distribution = ({ handleNext, handleBack }: Props) => { - return ( - - - - Distribution - - - - Safe Tokens will be distributed to stakeholders of the ecosystem - interested in shaping the future of Safe and smart-contract accounts. - - - Read full proposal - - - - - - - - - - palette.safeGreen.main, - color: lightPalette.text.primary, - }} - > - 60% - - - Community Treasuries - - - - 40% SafeDAO Treasury
- 15% GnosisDAO Treasury
- 5% Joint Treasury (GNO <> SAFE) -
-
- - - palette.secondary.light, - color: lightPalette.text.primary, - }} - > - 15% - - - Core Contributors - - - Current and future core contributor teams - - - - palette.info.main, - color: lightPalette.text.primary, - }} - > - 15% - - - Safe Foundation - - - - 8% strategic raise -
- 7% grants and reserve -
-
- - - palette.success.main, - color: ({ palette }) => palette.background.paper, - }} - > - 5% - - - Guardians - - - - 1.25% allocation -
- 1.25% vested allocation
- 2.5% future programs -
-
- - - palette.warning.main, - color: ({ palette }) => palette.background.paper, - }} - > - 5% - - - User - - - - 2.5% allocation -
- 2.5% vested allocation
-
-
-
- -
- ) -} - -export default Distribution diff --git a/apps/safe-claiming-app/src/components/steps/Government/index.tsx b/apps/safe-claiming-app/src/components/steps/Government/index.tsx deleted file mode 100644 index 95d80a951..000000000 --- a/apps/safe-claiming-app/src/components/steps/Government/index.tsx +++ /dev/null @@ -1,137 +0,0 @@ -import { - Accordion, - AccordionDetails, - AccordionSummary, - List, - ListItem, - Paper, - Typography, -} from "@mui/material" -import Box from "@mui/system/Box" - -import ExpandMoreIcon from "@mui/icons-material/ExpandMore" -import { NavButtons } from "src/components/helpers/NavButtons" -import InfoOutlined from "@mui/icons-material/InfoOutlined" - -type Props = { - handleBack: () => void - handleNext: () => void -} - -const InfoAccordion = ({ - summaryText, - details, -}: { - summaryText: string - details: string[] -}) => { - return ( - - } - sx={{ "&.Mui-expanded": { minHeight: "48px" } }} - > - palette.text.primary, - minWidth: "6px", - minHeight: "6px", - top: "9px", - left: "-24px", - }, - }} - > - {summaryText} - - - - - {details.map((detail: string) => ( - {detail} - ))} - - - - ) -} - -const Government = ({ handleNext, handleBack }: Props) => { - return ( - - - What exactly is the Safe token and what does it govern? - - - palette.secondary.main, - }} - /> - - Safe Token is initially non-transferable. - - - - $SAFE is an ERC-20 governance token that stewards infrastructure - components of the Safe ecosystem, including: - - - - - - - - - - - - - ) -} - -export default Government diff --git a/apps/safe-claiming-app/src/components/steps/Intro/index.tsx b/apps/safe-claiming-app/src/components/steps/Intro/index.tsx deleted file mode 100644 index 4ea00a1c1..000000000 --- a/apps/safe-claiming-app/src/components/steps/Intro/index.tsx +++ /dev/null @@ -1,70 +0,0 @@ -import { Box, Paper, Typography } from "@mui/material" -import { ReactComponent as SafeHeaderGradient } from "src/assets/images/safe-header-logo-gradient.svg" -import { NavButtons } from "src/components/helpers/NavButtons" - -type Props = { - handleNext: () => void -} - -const Intro = ({ handleNext }: Props) => { - return ( - <> - - - - Welcome to the next generation of digital ownership{" "} - -
    - - SafeDAO is on a mission to unlock digital ownership for everyone in - Web3. - - - {" "} - We will do this by establishing a universal standard for custody of - digital assets, data and identity with smart contract based - accounts. - - - You have been chosen to help govern the SafeDAO, and decide on the - future of Web3 ownership. Use this power wisely! - -
- - - -
- - ) -} - -export default Intro diff --git a/apps/safe-claiming-app/src/components/steps/NoAirdrop/index.tsx b/apps/safe-claiming-app/src/components/steps/NoAirdrop/index.tsx deleted file mode 100644 index 1fe0f93b7..000000000 --- a/apps/safe-claiming-app/src/components/steps/NoAirdrop/index.tsx +++ /dev/null @@ -1,55 +0,0 @@ -import { Button, Paper, Typography, useTheme } from "@mui/material" -import { ReactComponent as ExternalLink } from "src/assets/images/external_link.svg" -import { ReactComponent as SafeLogoHeader } from "src/assets/images/safe-header-logo-plain.svg" - -const NoAirdrop = () => { - const { palette } = useTheme() - return ( - <> - - - - Sorry, the airdrop is not available for you at the moment. - - - Want to learn how to participate more in future initiatives from Safe? -
- Check out more information at -
-
- - -
-
- - ) -} - -export default NoAirdrop diff --git a/apps/safe-claiming-app/src/components/steps/SafeInfo/index.tsx b/apps/safe-claiming-app/src/components/steps/SafeInfo/index.tsx deleted file mode 100644 index e3bec80d2..000000000 --- a/apps/safe-claiming-app/src/components/steps/SafeInfo/index.tsx +++ /dev/null @@ -1,92 +0,0 @@ -import { Card, Paper, styled, Typography } from "@mui/material" -import Box from "@mui/system/Box" - -import { ReactComponent as LockIcon } from "src/assets/images/lock.svg" -import { ReactComponent as ValueIcon } from "src/assets/images/assets.svg" -import { NavButtons } from "src/components/helpers/NavButtons" -import { useDarkMode } from "src/hooks/useDarkMode" - -type Props = { - handleBack: () => void - handleNext: () => void -} - -const InfoCard = styled(Card)<{ darkMode: boolean }>(({ darkMode, theme }) => ({ - backgroundColor: darkMode - ? theme.palette.background.paper - : theme.palette.safeGreen.light, - border: `${theme.palette.primary.main} 1px solid`, - borderRadius: "8px", - padding: "24px", - minWidth: "220px", -})) - -const SafeInfo = ({ handleNext, handleBack }: Props) => { - const isDarkMode = useDarkMode() - return ( - - - - What is Safe? - - - - Safe is critical infrastructure for Web3. It is a programmable account - standard that enables secure management of digital assets, data and - identity.
-
- With this token launch, Safe is now a community-driven ownership - platform. -
- - - - Total Safes created - - - - 167,414 - - - - - Total value protected - - - - $41B - - - - - - - Why are we launching a token? - - - As critical Web3 infrastructure, Safe needs to be a{" "} - community-owned, censorship resistant project, with a - committed ecosystem stewarding its decisions. A governance token is - needed to help coordinate this effort. - - -
- ) -} - -export default SafeInfo diff --git a/apps/safe-claiming-app/src/components/steps/SafeNavigation/index.tsx b/apps/safe-claiming-app/src/components/steps/SafeNavigation/index.tsx deleted file mode 100644 index f888a293c..000000000 --- a/apps/safe-claiming-app/src/components/steps/SafeNavigation/index.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import { Link, Paper, Typography, Card } from "@mui/material" -import Box from "@mui/system/Box" -import { ReactComponent as ExternalLink } from "src/assets/images/external_link.svg" - -import Checkmark from "@mui/icons-material/Check" -import { DISCORD_URL, FORUM_URL, GOVERNANCE_URL } from "src/config/constants" -import { NavButtons } from "src/components/helpers/NavButtons" - -type Props = { - handleBack: () => void - handleNext: () => void -} - -const SafeNavigation = ({ handleNext, handleBack }: Props) => { - return ( - - - - Navigating SafeDAO - - - - SafeDAO aims to foster a vibrant ecosystem of applications and wallets - leveraging Safe smart contract accounts. This will be achieved through - data-backed discussions, grants, ecosystem investments, as well as - providing developer tools and infrastructure. - - - - How to get involved: - - - - - - - Discuss SafeDAO improvements - post topics and discuss in our{" "} - - Forum - - - - - - - - Propose improvements - read our governance{" "} - - process - - {" "} - and post an SEP. - - - - - Govern improvements - vote on our Snapshot. - - - - - Chat with the community - join our Safe{" "} - - Discord - - - . - - - palette.safeGreen.light, - border: ({ palette }) => `1px solid ${palette.primary.main}`, - borderRadius: "8px", - padding: 3, - minWidth: "220px", - }} - > - - Now… -
- Help decide on the future of ownership with $SAFE. -
-
-
- - -
- ) -} - -export default SafeNavigation diff --git a/apps/safe-claiming-app/src/components/steps/Success.tsx b/apps/safe-claiming-app/src/components/steps/Success.tsx deleted file mode 100644 index 6b04c49b8..000000000 --- a/apps/safe-claiming-app/src/components/steps/Success.tsx +++ /dev/null @@ -1,105 +0,0 @@ -import { Box, Paper, Typography } from "@mui/material" -import { ReactComponent as SafeHeaderGradient } from "src/assets/images/safe-header-logo-gradient.svg" -import TwitterIcon from "@mui/icons-material/Twitter" -import styled from "@emotion/styled" -import { AppState } from "src/App" -import { NavButtons } from "../helpers/NavButtons" -import { formatAmount } from "src/utils/format" -import { useDarkMode } from "src/hooks/useDarkMode" -const TweetBox = styled(Box)` - background: white; - border-radius: 8px; - margin-bottom: 16px; -` - -const StyledTwitterIcon = styled(TwitterIcon)` - fill: #00b4f7; -` - -const StyledTwitterButton = styled.a` - font-size: 14px; - font-weight: bold; - background-color: #00b4f7; - padding: 8px 24px; - color: white; - border-radius: 40px; - text-decoration: none; - margin-top: 8px; - - &:hover { - background-color: #05a2dc; - } -` - -const Success = ({ - handleBack, - state, -}: { - state: AppState - handleBack: () => void -}) => { - const formattedTokenAmount = formatAmount( - Number(state.claimedAmount ?? "0"), - 2 - ) - const isDarkMode = useDarkMode() - - const isCustomDelegate = !state.delegateData.some( - (delegate) => delegate.address === state.delegate?.address - ) - - const tweetText = isCustomDelegate - ? "I've just claimed my Safe governance tokens to help steward the public good that is @Safe 🔰🫡 🔰🫡" - : state.delegate?.ens - ? `I've just claimed my Safe governance tokens and delegated my voting power to ${state.delegate.ens} to help steward the public good that is @Safe 🔰🫡` - : "I've just claimed my Safe governance tokens and delegated my voting power to help steward the public good that is @Safe 🔰🫡" - - const tweetURL = encodeURI( - `https://twitter.com/intent/tweet?text=${tweetText}` - ) - - return ( - - - - Congrats! - - - You successfully started claiming{" "} - {formattedTokenAmount} Tokens!
- Once the transaction is signed and executed, the tokens -
- will be available in your Safe. -
- Share your claim on Twitter: - - - - {tweetText} - - - Tweet - - - -
- ) -} - -export default Success diff --git a/apps/safe-claiming-app/src/config/colors-dark.ts b/apps/safe-claiming-app/src/config/colors-dark.ts deleted file mode 100644 index f707d9b64..000000000 --- a/apps/safe-claiming-app/src/config/colors-dark.ts +++ /dev/null @@ -1,32 +0,0 @@ -const darkPalette = { - text: { - primary: "#FFFFFF", - secondary: "#636669", - disabled: "#636669", - }, - safeGreen: { - main: "#12FF80", - light: "#1E2A23", - }, - primary: { - dark: "#0cb259", - main: "#12FF80", - light: "#A1A3A7", - }, - background: { - default: "#121312", - paper: "#1C1C1C", - main: "#121312", - }, - secondary: { - main: "#FFFFFF", - light: "#12FF80", - }, - border: { - main: "#636669", - light: "#303033", - background: "#121312", - }, -} - -export default darkPalette diff --git a/apps/safe-claiming-app/src/config/colors.ts b/apps/safe-claiming-app/src/config/colors.ts deleted file mode 100644 index 818c208b3..000000000 --- a/apps/safe-claiming-app/src/config/colors.ts +++ /dev/null @@ -1,33 +0,0 @@ -const palette = { - text: { - primary: "#121312", - secondary: "#A1A3A7", - disabled: "#DDDEE0", - }, - safeGreen: { - dark: "#1E2A23", - main: "#12ff80", - light: "#EFFFF4", - }, - primary: { - dark: "#3c3c3c", - main: "#121312", - light: "#636669", - }, - background: { - default: "#F6F7F8", - paper: "#FFFFFF", - main: "#F4F4F4", - }, - secondary: { - main: "#B2BBC0", - light: "#B0FFC9", - }, - border: { - main: "#A1A3A7", - light: "#DCDEE0", - background: "#F4F4F4", - }, -} - -export default palette diff --git a/apps/safe-claiming-app/src/config/constants.ts b/apps/safe-claiming-app/src/config/constants.ts deleted file mode 100644 index 566dd1023..000000000 --- a/apps/safe-claiming-app/src/config/constants.ts +++ /dev/null @@ -1,66 +0,0 @@ -import { BigNumber } from "ethers" - -type ChainConstants = { - DELEGATE_ID: string - SAFE_TOKEN_ADDRESS: string -} - -export enum Chains { - RINKEBY = 4, - GOERLI = 5, - MAINNET = 1, -} - -export const STORAGE_PREFIX = "SAFE__" - -export const MAX_UINT128 = BigNumber.from("0xFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF") - -export const ZERO_ADDRESS = "0x0000000000000000000000000000000000000000" - -const isProdEnv = process.env.NODE_ENV === "production" - -export const WEB_APP_URL = isProdEnv - ? "https://app.safe.global" - : "https://safe-web-core.dev.5afe.dev" - -export const CLAIMING_APP_URL = isProdEnv - ? "https://apps.gnosis-safe.io/safe-claiming-app" - : "https://safe-apps.dev.5afe.dev/safe-claiming-app" - -export const CLAIMING_DATA_URL = isProdEnv - ? "https://safe-claiming-app-data.gnosis-safe.io" - : "https://safe-claiming-app-data.staging.5afe.dev" - -export const DelegateRegistryAddress = - "0x469788fe6e9e9681c6ebf3bf78e7fd26fc015446" - -export const GUARDIANS_URL = `${CLAIMING_DATA_URL}/guardians/guardians.json` - -export const GUARDIANS_IMAGE_URL = `${CLAIMING_DATA_URL}/guardians/images/` - -export const VESTING_URL = `${CLAIMING_DATA_URL}/allocations/` - -export const FORUM_URL = "https://forum.safe.global" - -export const DISCORD_URL = "https://discord.gg/gXK3gt8w3D" - -export const GOVERNANCE_URL = - "https://forum.gnosis-safe.io/t/how-to-safedao-governance-process/846" - -export const FULL_PROPOSAL_URL = - "https://forum.gnosis-safe.io/t/safe-voting-power-and-circulating-supply/558" - -export const CHAIN_CONSTANTS: Record = { - 1: { - DELEGATE_ID: "safe.eth", - SAFE_TOKEN_ADDRESS: "0x5afe3855358e112b5647b952709e6165e1c1eeee", - }, - 4: { - DELEGATE_ID: "tutis.eth", - SAFE_TOKEN_ADDRESS: "0xCFf1b0FdE85C102552D1D96084AF148f478F964A", - }, - 5: { - DELEGATE_ID: "tutis.eth", - SAFE_TOKEN_ADDRESS: "0x61fD3b6d656F39395e32f46E2050953376c3f5Ff", - }, -} diff --git a/apps/safe-claiming-app/src/config/theme.ts b/apps/safe-claiming-app/src/config/theme.ts deleted file mode 100644 index 946eb1a23..000000000 --- a/apps/safe-claiming-app/src/config/theme.ts +++ /dev/null @@ -1,199 +0,0 @@ -import { createTheme } from "@mui/material" - -import palette from "./colors" -import darkPalette from "./colors-dark" - -declare module "@mui/material/styles" { - interface Palette { - safeGreen: Palette["primary"] - safeGrey: Palette["primary"] - border: Palette["primary"] - } - interface PaletteOptions { - safeGreen: PaletteOptions["primary"] - safeGrey: PaletteOptions["primary"] - border: PaletteOptions["primary"] - } - - interface PaletteColor { - background?: string - } -} - -const initTheme = (darkMode: boolean) => { - const colors = darkMode ? darkPalette : palette - - return createTheme({ - palette: { - mode: darkMode ? "dark" : "light", - safeGrey: { - main: "rgb(161, 163, 167)", - }, - info: { - main: "#5FDDFF", - }, - success: { - main: "#00B460", - }, - warning: { - main: "#FF8061", - }, - ...colors, - }, - typography: { - fontFamily: "DM Sans, Roboto, sans-serif", - h1: { - fontSize: "2rem", - fontWeight: "bold", - }, - h2: { - fontSize: "1.75rem", - fontWeight: "bold", - }, - h3: { - fontSize: "1.5rem", - fontWeight: "bold", - }, - h4: { - fontSize: "27px", - fontWeight: "bold", - }, - h5: { - fontSize: "24px", - fontWeight: "bold", - color: colors.text.primary, - }, - button: { - textTransform: "none", - fontWeight: "bold", - }, - subtitle1: { - fontStyle: "normal", - fontWeight: 400, - fontSize: "16px", - lineHeight: "24px", - }, - subtitle2: { - fontStyle: "normal", - fontWeight: 400, - fontSize: "14px", - lineHeight: "24px", - color: colors.text.secondary, - }, - caption: { - fontSize: "12px", - lineHeight: "16px", - letterSpacing: "0.4px", - }, - }, - shape: { - borderRadius: 8, - }, - components: { - MuiCard: { - styleOverrides: { - root: ({ theme }) => ({ - borderRadius: theme.shape.borderRadius, - background: theme.palette.background.paper, - }), - }, - }, - MuiTooltip: { - styleOverrides: { - tooltip: { - backgroundColor: colors.background.default, - color: colors.text.primary, - fontSize: 12, - paddingTop: "12px", - paddingBottom: "8px", - paddingLeft: "16px", - paddingRight: "16px", - maxWidth: "275px", - boxShadow: "rgba(33, 48, 77, 0.1) 0px 0px 10px 0px", - }, - arrow: { - color: colors.text.primary, - }, - }, - }, - MuiPaper: { - styleOverrides: { - elevation: { - zIndex: 2, - color: colors.text.primary, - }, - }, - }, - MuiDivider: { - styleOverrides: { - light: { - borderColor: "#EEEFF0", - }, - }, - }, - MuiButton: { - styleOverrides: { - sizeLarge: { - padding: "10px 40px", - }, - root: ({ theme }) => ({ - borderRadius: theme.shape.borderRadius, - fontWeight: "bold", - lineHeight: 1.25, - borderColor: theme.palette.primary.main, - textTransform: "none", - "&:hover": { - boxShadow: "none", - }, - }), - }, - }, - MuiLink: { - styleOverrides: { - root: { - fontWeight: 700, - "&:hover": { - color: "#12ff80", - }, - }, - }, - }, - MuiAvatar: { - styleOverrides: { - img: { - objectFit: "contain", - }, - }, - }, - MuiAccordion: { - styleOverrides: { - root: { - borderColor: colors.primary.light, - border: "1px solid", - "&.Mui-expanded": { - backgroundColor: colors.safeGreen.light, - borderColor: colors.primary.main, - }, - }, - }, - }, - MuiCssBaseline: { - styleOverrides: ` - @font-face { - font-family: 'DM Sans'; - font-display: swap; - font-weight: 400; - src: url('/safe-claiming-app/fonts/dm-sans-v11-latin-ext-regular.woff2') format('woff2'); - } - @font-face { - font-family: 'DM Sans'; - font-display: swap; - font-weight: bold; - src: url('/safe-claiming-app/fonts/dm-sans-v11-latin-ext-700.woff2') format('woff2'); - }`, - }, - }, - }) -} - -export default initTheme diff --git a/apps/safe-claiming-app/src/hooks/__tests__/useAmounts.test.ts b/apps/safe-claiming-app/src/hooks/__tests__/useAmounts.test.ts deleted file mode 100644 index 7c781a199..000000000 --- a/apps/safe-claiming-app/src/hooks/__tests__/useAmounts.test.ts +++ /dev/null @@ -1,160 +0,0 @@ -import { renderHook } from "@testing-library/react-hooks" -import { ethers } from "ethers" -import { act } from "react-dom/test-utils" -import { VestingClaim } from "src/types/vestings" -import { DESYNC_BUFFER } from "src/utils/vesting" -import { useAmounts } from "../useAmounts" - -const fakeNow = new Date() - -const ONE_WEEK = 7 * 24 * 60 * 60 - -const createMockVesting = ( - durationWeeks: number, - amount: number, - vestingStartDiffInWeeks: number, - claimedAmount?: number -): VestingClaim => { - return { - proof: [], - isRedeemed: false, - amountClaimed: claimedAmount - ? ethers.utils.parseEther(claimedAmount.toString()).toString() - : "0", - vestingId: "0x01", - account: ethers.utils.hexZeroPad("0x1", 20), - amount: ethers.utils.parseEther(amount.toString()).toString(), - curve: 0, - durationWeeks, - chainId: 4, - contract: ethers.utils.hexZeroPad("0x2", 20), - tag: "user", - startDate: - Math.floor(fakeNow.getTime() / 1000) - - DESYNC_BUFFER + - ONE_WEEK * vestingStartDiffInWeeks, - } -} - -describe("useAmounts()", () => { - beforeAll(() => { - jest.useFakeTimers() - }) - - afterAll(() => { - jest.useRealTimers() - }) - - beforeEach(() => { - jest.setSystemTime(fakeNow) - }) - it("should return 0 without vesting claim", () => { - const result = renderHook(() => useAmounts(null)) - expect(result.result.current).toEqual(["0", "0"]) - }) - - it("fully vested amount", () => { - const vestingClaim: VestingClaim = createMockVesting(1, 1000, -1) - const result = renderHook(() => useAmounts(vestingClaim)) - expect(result.result.current).toEqual([ - ethers.utils.parseEther("1000").toString(), - "0", - ]) - }) - - it("vested amount is smaller than amount claimed", () => { - // vesting is just half vested but already more than half is claimed (10 more than half) - const vestingClaim: VestingClaim = createMockVesting(2, 1000, -1, 510) - const result = renderHook(() => useAmounts(vestingClaim)) - // nothing is claimable and 490 are in vesting - expect(result.result.current).toEqual([ - ethers.utils.parseEther("0").toString(), - ethers.utils.parseEther("490").toString(), - ]) - }) - - it("vesting did not start yet", () => { - const vestingClaim: VestingClaim = createMockVesting(1, 1000, 1) - const result = renderHook(() => useAmounts(vestingClaim)) - expect(result.result.current).toEqual([ - "0", - ethers.utils.parseEther("1000").toString(), - ]) - }) - - it("vesting fully vested and fully claimed", () => { - const vestingClaim: VestingClaim = createMockVesting(1, 1000, -1, 1000) - const result = renderHook(() => useAmounts(vestingClaim)) - expect(result.result.current).toEqual([ - ethers.utils.parseEther("0").toString(), - "0", - ]) - }) - - it("vesting fully vested and partly claimed", () => { - const vestingClaim: VestingClaim = createMockVesting(1, 1000, -1, 500) - const result = renderHook(() => useAmounts(vestingClaim)) - expect(result.result.current).toEqual([ - ethers.utils.parseEther("500").toString(), - "0", - ]) - }) - - it("vesting half vested", () => { - const vestingClaim: VestingClaim = createMockVesting(2, 1000, -1) - const result = renderHook(() => useAmounts(vestingClaim)) - expect(result.result.current).toEqual([ - ethers.utils.parseEther("500").toString(), - ethers.utils.parseEther("500").toString(), - ]) - }) - - it("vesting half vested and quarter claimed", () => { - const vestingClaim: VestingClaim = createMockVesting(2, 1000, -1, 250) - const result = renderHook(() => useAmounts(vestingClaim)) - expect(result.result.current).toEqual([ - ethers.utils.parseEther("250").toString(), - ethers.utils.parseEther("500").toString(), - ]) - }) - - it("vesting half vested and quarter claimed", () => { - const vestingClaim: VestingClaim = createMockVesting(2, 1000, -1, 250) - const result = renderHook(() => useAmounts(vestingClaim)) - expect(result.result.current).toEqual([ - ethers.utils.parseEther("250").toString(), - ethers.utils.parseEther("500").toString(), - ]) - }) - - it("test polling and updating of vesting", () => { - // vests 1 token per second - const vestingClaim = createMockVesting(1, ONE_WEEK, 0) - const result = renderHook(() => useAmounts(vestingClaim)) - // Initially nothing is vested and 1000 are in vesting - expect(result.result.current).toEqual([ - ethers.utils.parseEther("0").toString(), - ethers.utils.parseEther(ONE_WEEK.toString()).toString(), - ]) - - // advance to next polling - act(() => { - jest.advanceTimersByTime(10_000) - }) - - expect(result.result.current).toEqual([ - ethers.utils.parseEther("10").toString(), - ethers.utils.parseEther((ONE_WEEK - 10).toString()).toString(), - ]) - - // Poll 9 more times - act(() => { - jest.advanceTimersByTime(90_000) - }) - - expect(result.result.current).toEqual([ - ethers.utils.parseEther("100").toString(), - ethers.utils.parseEther((ONE_WEEK - 100).toString()).toString(), - ]) - }) -}) diff --git a/apps/safe-claiming-app/src/hooks/__tests__/useDelegate.test.ts b/apps/safe-claiming-app/src/hooks/__tests__/useDelegate.test.ts deleted file mode 100644 index a56e1f8af..000000000 --- a/apps/safe-claiming-app/src/hooks/__tests__/useDelegate.test.ts +++ /dev/null @@ -1,105 +0,0 @@ -import { waitFor } from "@testing-library/react" -import { renderHook } from "@testing-library/react-hooks" -import { ethers } from "ethers" -import { - CHAIN_CONSTANTS, - DelegateRegistryAddress, - ZERO_ADDRESS, -} from "src/config/constants" -import { getWeb3Provider } from "src/utils/getWeb3Provider" -import { useDelegate } from "../useDelegate" - -const SAFE_ADDRESS = "0x6a13e0280740cc5bd35eeee33b470b5bbb93df37" - -const mockWeb3Provider = { - _isProvider: true, - call: jest.fn(() => Promise.reject("call")), -} -jest.mock("@safe-global/safe-apps-react-sdk", () => { - const originalModule = jest.requireActual("@safe-global/safe-apps-react-sdk") - return { - __esModule: true, - // We require some of the enums/types from the original module - ...originalModule, - useSafeAppsSDK: () => ({ - safe: { - safeAddress: "0x6a13E0280740CC5bd35eeee33B470b5bBb93dF37", - chainId: 4, - }, - sdk: undefined, - }), - } -}) - -jest.mock("src/utils/getWeb3Provider", () => ({ - getWeb3Provider: () => mockWeb3Provider, -})) - -describe("useDelegate()", () => { - const web3Provider = getWeb3Provider(undefined as never, undefined as never) - - afterAll(() => { - jest.unmock("src/utils/getWeb3Provider") - jest.unmock("@safe-global/safe-apps-react-sdk") - }) - - it("ignore the ZERO_ADDRESS as delegate", async () => { - const delegateIDInBytes = ethers.utils.formatBytes32String( - CHAIN_CONSTANTS[4].DELEGATE_ID - ) - - web3Provider.call = jest.fn((transaction) => { - expect(transaction.to?.toString().toLowerCase()).toEqual( - DelegateRegistryAddress.toLowerCase() - ) - expect(transaction.data?.toString().toLowerCase()).toContain( - SAFE_ADDRESS.toLowerCase().slice(2) - ) - expect(transaction.data?.toString().toLowerCase()).toContain( - delegateIDInBytes.toLowerCase().slice(2) - ) - return Promise.resolve(ethers.utils.hexZeroPad(ZERO_ADDRESS, 32)) - }) - - const result = renderHook(() => useDelegate()) - - expect(result.result.current).toBeUndefined() - - await waitFor(() => { - expect(mockWeb3Provider.call).toBeCalledTimes(1) - }) - - expect(result.result.current).toBeUndefined() - }) - - it("should encode the correct data and fetch the delegate on-chain once", async () => { - const delegateIDInBytes = ethers.utils.formatBytes32String( - CHAIN_CONSTANTS[4].DELEGATE_ID - ) - const delegateAddress = ethers.utils.hexZeroPad("0x1", 20) - - web3Provider.call = jest.fn((transaction) => { - expect(transaction.to?.toString().toLowerCase()).toEqual( - DelegateRegistryAddress.toLowerCase() - ) - expect(transaction.data?.toString().toLowerCase()).toContain( - SAFE_ADDRESS.toLowerCase().slice(2) - ) - expect(transaction.data?.toString().toLowerCase()).toContain( - delegateIDInBytes.toLowerCase().slice(2) - ) - return Promise.resolve(ethers.utils.hexZeroPad(delegateAddress, 32)) - }) - - const result = renderHook(() => useDelegate()) - - expect(result.result.current).toBeUndefined() - - await waitFor(() => { - expect(result.result.current).not.toBeUndefined() - }) - - expect(result.result.current).toEqual(delegateAddress) - expect(mockWeb3Provider.call).toBeCalledTimes(1) - }) -}) diff --git a/apps/safe-claiming-app/src/hooks/__tests__/useEnsResolution.test.tsx b/apps/safe-claiming-app/src/hooks/__tests__/useEnsResolution.test.tsx deleted file mode 100644 index ba162afdd..000000000 --- a/apps/safe-claiming-app/src/hooks/__tests__/useEnsResolution.test.tsx +++ /dev/null @@ -1,352 +0,0 @@ -import { renderHook, act } from "@testing-library/react-hooks" -import { waitFor } from "@testing-library/react" -import { useEnsResolution } from "src/hooks/useEnsResolution" -import { getWeb3Provider } from "src/utils/getWeb3Provider" - -const mockWeb3Provider = { - resolveName: jest.fn(() => Promise.reject("resolveName")), -} - -jest.mock("@safe-global/safe-apps-react-sdk", () => { - const originalModule = jest.requireActual("@safe-global/safe-apps-react-sdk") - return { - __esModule: true, - // We require some of the enums/types from the original module - ...originalModule, - useSafeAppsSDK: () => ({ - safe: { - chainId: 1, - safeAddress: "0x2000000000000000000000000000000000000000", - }, - sdk: undefined, - }), - } -}) - -jest.mock("src/utils/getWeb3Provider", () => ({ - getWeb3Provider: () => mockWeb3Provider, -})) - -describe("useEnsResolution()", () => { - const web3Provider = getWeb3Provider(undefined as never, undefined as never) - - afterAll(() => { - jest.unmock("src/utils/getWeb3Provider") - jest.unmock("@safe-global/safe-apps-react-sdk") - }) - - afterEach(() => { - jest.useRealTimers() - }) - - it("should return valid addresses immediately and not trigger ens resolution", async () => { - const validAddress = "0x1000000000000000000000000000000000000000" - web3Provider.resolveName = jest.fn() - jest.useFakeTimers() - const { result } = renderHook(() => useEnsResolution(validAddress)) - - expect(result.current[0]).toEqual({ address: validAddress }) - expect(result.current[1]).toBeUndefined() - expect(result.current[2]).toBeFalsy() - expect(web3Provider.resolveName).not.toHaveBeenCalled() - - jest.advanceTimersByTime(301) - expect(web3Provider.resolveName).not.toHaveBeenCalled() - }) - - it("should accept EIP 3770 addresses with correct chain prefix", async () => { - const validAddress = "eth:0x1000000000000000000000000000000000000000" - web3Provider.resolveName = jest.fn() - jest.useFakeTimers() - const { result } = renderHook(() => useEnsResolution(validAddress)) - - expect(result.current[0]).toEqual({ address: validAddress.slice(4) }) - expect(result.current[1]).toBeUndefined() - expect(result.current[2]).toBeFalsy() - expect(web3Provider.resolveName).not.toHaveBeenCalled() - - jest.advanceTimersByTime(301) - expect(web3Provider.resolveName).not.toHaveBeenCalled() - }) - - it("should not accept EIP 3770 addresses with wrong chain prefix", async () => { - const validAddress = "rin:0x1000000000000000000000000000000000000000" - web3Provider.resolveName = jest.fn() - jest.useFakeTimers() - const { result } = renderHook(() => useEnsResolution(validAddress)) - - expect(result.current[0]).toBeUndefined() - expect(result.current[1]).toEqual("The chain prefix needs to be eth:") - expect(web3Provider.resolveName).not.toHaveBeenCalled() - - jest.advanceTimersByTime(301) - expect(web3Provider.resolveName).not.toHaveBeenCalled() - }) - - it("should checksum manual addresses and not trigger ens resolution", async () => { - const checksummedAddress = "0x571a651965976752BaF5832B545794dD50e766ba" - web3Provider.resolveName = jest.fn() - jest.useFakeTimers() - const { result } = renderHook(() => - useEnsResolution(checksummedAddress.toLowerCase()) - ) - - expect(result.current[0]).toEqual({ address: checksummedAddress }) - expect(result.current[1]).toBeUndefined() - expect(result.current[2]).toBeFalsy() - expect(web3Provider.resolveName).not.toHaveBeenCalled() - - jest.advanceTimersByTime(301) - expect(web3Provider.resolveName).not.toHaveBeenCalled() - }) - - it("should return immediately for empty strings and not trigger ens resolution", async () => { - web3Provider.resolveName = jest.fn() - jest.useFakeTimers() - const { result } = renderHook(() => useEnsResolution("")) - - expect(result.current[0]).toBeUndefined() - expect(result.current[1]).toBeUndefined() - expect(result.current[2]).toBeFalsy() - expect(web3Provider.resolveName).not.toHaveBeenCalled() - - jest.advanceTimersByTime(301) - expect(web3Provider.resolveName).not.toHaveBeenCalled() - }) - - it("should set error for unexpected errors during ENS resolution", async () => { - web3Provider.resolveName = jest.fn(() => - Promise.reject("Unexpected errors.") - ) - jest.useFakeTimers() - const { result } = renderHook(() => useEnsResolution("test.eth")) - - expect(result.current[0]).toBeUndefined() - expect(result.current[1]).toBeUndefined() - expect(result.current[2]).toBeFalsy() - expect(web3Provider.resolveName).not.toHaveBeenCalled() - - act(() => { - jest.advanceTimersByTime(301) - }) - - await waitFor(() => { - expect(result.current[2]).toBeFalsy() - }) - - expect(web3Provider.resolveName).toHaveBeenCalled() - expect(result.current[0]).toBeUndefined() - expect(result.current[1]).toEqual("Error while resolving ENS") - expect(result.current[2]).toBeFalsy() - }) - - it("should resolve ENS names after 300ms", async () => { - const resolvedAddress = "0x1000000000000000000000000000000000000000" - web3Provider.resolveName = jest.fn(() => Promise.resolve(resolvedAddress)) - - jest.useFakeTimers() - const { result } = renderHook(() => useEnsResolution("test.eth")) - - expect(result.current[0]).toBeUndefined() - expect(result.current[1]).toBeUndefined() - expect(result.current[2]).toBeFalsy() - expect(web3Provider.resolveName).not.toHaveBeenCalled() - - jest.advanceTimersByTime(299) - - expect(result.current[0]).toBeUndefined() - expect(result.current[1]).toBeUndefined() - expect(result.current[2]).toBeFalsy() - expect(web3Provider.resolveName).not.toHaveBeenCalled() - - // this should trigger the timer - act(() => { - jest.advanceTimersByTime(2) - }) - expect(result.current[2]).toBeTruthy() - // wait to resolve the promise - await waitFor(() => { - expect(result.current[2]).toBeFalsy() - }) - expect(web3Provider.resolveName).toHaveBeenCalledTimes(1) - expect(result.current[0]).not.toBeUndefined() - expect(result.current[1]).toBeUndefined() - expect(result.current[2]).toBeFalsy() - }) - - it("should debounce ENS resolution", async () => { - const resolvedAddress = "0x1000000000000000000000000000000000000000" - web3Provider.resolveName = jest.fn(() => Promise.resolve(resolvedAddress)) - - jest.useFakeTimers() - - const { result, rerender } = renderHook( - ({ ensName }) => useEnsResolution(ensName), - { initialProps: { ensName: "test.eth" } } - ) - - expect(result.current[0]).toBeUndefined() - expect(result.current[1]).toBeUndefined() - expect(result.current[2]).toBeFalsy() - expect(web3Provider.resolveName).not.toHaveBeenCalled() - - jest.advanceTimersByTime(299) - - expect(result.current[0]).toBeUndefined() - expect(result.current[1]).toBeUndefined() - expect(result.current[2]).toBeFalsy() - expect(web3Provider.resolveName).not.toHaveBeenCalled() - - rerender({ ensName: "test2.eth" }) - - act(() => { - jest.advanceTimersByTime(299) - }) - - expect(result.current[0]).toBeUndefined() - expect(result.current[1]).toBeUndefined() - expect(result.current[2]).toBeFalsy() - expect(web3Provider.resolveName).not.toHaveBeenCalled() - - // this should trigger the resolution - act(() => { - jest.advanceTimersByTime(2) - }) - expect(result.current[2]).toBeTruthy() - // wait to resolve the promise - await waitFor(() => { - expect(result.current[2]).toBeFalsy() - }) - expect(web3Provider.resolveName).toHaveBeenCalledTimes(1) - expect(result.current[0]).toEqual({ - address: resolvedAddress, - ens: "test2.eth", - }) - expect(result.current[1]).toBeUndefined() - expect(result.current[2]).toBeFalsy() - }) - - it("should trigger ens resolution multiple times when slowly typing", async () => { - const resolvedAddress = "0x1000000000000000000000000000000000000000" - web3Provider.resolveName = jest - .fn() - .mockImplementationOnce(() => Promise.resolve(null)) - .mockImplementationOnce(() => Promise.resolve(null)) - .mockImplementationOnce(() => Promise.resolve(resolvedAddress)) - - jest.useFakeTimers() - - const { result, rerender } = renderHook( - ({ ensName }) => useEnsResolution(ensName), - { initialProps: { ensName: "t" } } - ) - act(() => { - jest.advanceTimersByTime(310) - }) - await waitFor(() => { - expect(result.current[2]).toBeFalsy() - }) - - expect(web3Provider.resolveName).toHaveBeenCalledTimes(1) - expect(result.current[0]).toBeUndefined() - expect(result.current[1]).toEqual("Invalid address / ENS name") - expect(result.current[2]).toBeFalsy() - - rerender({ ensName: "te" }) - - act(() => { - jest.advanceTimersByTime(120) - }) - - rerender({ ensName: "tes" }) - - act(() => { - jest.advanceTimersByTime(120) - }) - - rerender({ ensName: "test" }) - - act(() => { - jest.advanceTimersByTime(320) - }) - - await waitFor(() => { - expect(result.current[2]).toBeFalsy() - }) - - expect(result.current[0]).toBeUndefined() - expect(result.current[1]).toEqual("Invalid address / ENS name") - expect(result.current[2]).toBeFalsy() - - rerender({ ensName: "test." }) - - act(() => { - jest.advanceTimersByTime(120) - }) - - rerender({ ensName: "test.e" }) - - act(() => { - jest.advanceTimersByTime(120) - }) - - rerender({ ensName: "test.et" }) - - act(() => { - jest.advanceTimersByTime(120) - }) - - rerender({ ensName: "test.eth" }) - - act(() => { - jest.advanceTimersByTime(310) - }) - - await waitFor(() => { - expect(result.current[2]).toBeFalsy() - }) - - expect(web3Provider.resolveName).toHaveBeenCalledTimes(3) - - expect(result.current[0]).toEqual({ - address: resolvedAddress, - ens: "test.eth", - }) - expect(result.current[1]).toBeUndefined() - expect(result.current[2]).toBeFalsy() - }) - - it("should set error if resolved address is the same as the current safe address", async () => { - const resolvedAddress = "0x2000000000000000000000000000000000000000" - web3Provider.resolveName = jest.fn(() => Promise.resolve(resolvedAddress)) - jest.useFakeTimers() - const { result } = renderHook(() => useEnsResolution("test.eth")) - - expect(result.current[0]).toBeUndefined() - expect(result.current[1]).toBeUndefined() - expect(result.current[2]).toBeFalsy() - expect(web3Provider.resolveName).not.toHaveBeenCalled() - - act(() => { - jest.advanceTimersByTime(301) - }) - - await waitFor(() => { - expect(result.current[2]).toBeFalsy() - }) - - expect(web3Provider.resolveName).toHaveBeenCalled() - expect(result.current[0]).toBeUndefined() - expect(result.current[1]).toEqual("You can't delegate to your own Safe") - expect(result.current[2]).toBeFalsy() - }) - - it("should set error if typed address is the same as the current safe address", async () => { - const resolvedAddress = "0x2000000000000000000000000000000000000000" - jest.useFakeTimers() - const { result } = renderHook(() => useEnsResolution(resolvedAddress)) - - expect(result.current[1]).toEqual("You can't delegate to your own Safe") - expect(result.current[2]).toBeFalsy() - }) -}) diff --git a/apps/safe-claiming-app/src/hooks/__tests__/useIsTokenPaused.test.ts b/apps/safe-claiming-app/src/hooks/__tests__/useIsTokenPaused.test.ts deleted file mode 100644 index 84e43b8e7..000000000 --- a/apps/safe-claiming-app/src/hooks/__tests__/useIsTokenPaused.test.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { waitFor } from "@testing-library/react" -import { renderHook } from "@testing-library/react-hooks" -import { SafeToken__factory } from "src/types/contracts/factories/SafeToken__factory" -import { getWeb3Provider } from "src/utils/getWeb3Provider" -import { useIsTokenPaused } from "../useIsTokenPaused" - -const mockWeb3Provider = { - _isProvider: true, - call: jest.fn(() => Promise.reject("call")), -} -jest.mock("@safe-global/safe-apps-react-sdk", () => { - const originalModule = jest.requireActual("@safe-global/safe-apps-react-sdk") - return { - __esModule: true, - // We require some of the enums/types from the original module - ...originalModule, - useSafeAppsSDK: () => ({ - safe: { - safeAddress: "0x6a13E0280740CC5bd35eeee33B470b5bBb93dF37", - chainId: 4, - }, - sdk: undefined, - }), - } -}) - -jest.mock("src/utils/getWeb3Provider", () => ({ - getWeb3Provider: () => mockWeb3Provider, -})) - -describe("useIsTokenPaused", () => { - const web3Provider = getWeb3Provider(undefined as never, undefined as never) - const mockCall = jest.fn() - web3Provider.call = mockCall - - afterAll(() => { - jest.unmock("src/utils/getWeb3Provider") - jest.unmock("@safe-global/safe-apps-react-sdk") - }) - - it("should return true initially", () => { - mockCall.mockImplementation(() => Promise.resolve) - const { result } = renderHook(() => useIsTokenPaused()) - expect(result.current).toBeTruthy() - expect(mockCall).toBeCalledTimes(0) - }) - - it("should return true on error", async () => { - mockCall.mockImplementation(() => Promise.reject("ERROR")) - const { result } = renderHook(() => useIsTokenPaused()) - await waitFor(() => { - expect(result.current).toBeTruthy() - expect(mockCall).toBeCalledTimes(1) - }) - }) - - it("should return true if token is paused", async () => { - mockCall.mockImplementation(async () => - Promise.resolve( - SafeToken__factory.createInterface().encodeFunctionResult("paused", [ - true, - ]) - ) - ) - const { result } = renderHook(() => useIsTokenPaused()) - await waitFor(() => { - expect(result.current).toBeTruthy() - expect(mockCall).toBeCalledTimes(1) - }) - }) - - it("should return false if token is unpaused", async () => { - mockCall.mockImplementation(async () => - Promise.resolve( - SafeToken__factory.createInterface().encodeFunctionResult("paused", [ - false, - ]) - ) - ) - const { result } = renderHook(() => useIsTokenPaused()) - - await waitFor(() => { - expect(result.current).toBeFalsy() - expect(mockCall).toBeCalledTimes(1) - }) - }) -}) diff --git a/apps/safe-claiming-app/src/hooks/__tests__/useSafeTokenAllocation.test.ts b/apps/safe-claiming-app/src/hooks/__tests__/useSafeTokenAllocation.test.ts deleted file mode 100644 index 15e57cb59..000000000 --- a/apps/safe-claiming-app/src/hooks/__tests__/useSafeTokenAllocation.test.ts +++ /dev/null @@ -1,485 +0,0 @@ -import { waitFor } from "@testing-library/react" -import { renderHook } from "@testing-library/react-hooks" -import { - defaultAbiCoder, - hexZeroPad, - keccak256, - parseEther, - toUtf8Bytes, -} from "ethers/lib/utils" -import useSafeTokenAllocation from "../useSafeTokenAllocation" -import { BigNumber } from "ethers" -import { getWeb3Provider } from "src/utils/getWeb3Provider" -import { ZERO_ADDRESS } from "src/config/constants" - -const mockWeb3Provider = { - _isProvider: true, - call: jest.fn(() => Promise.reject("call")), -} -jest.mock("@safe-global/safe-apps-react-sdk", () => { - const originalModule = jest.requireActual("@safe-global/safe-apps-react-sdk") - return { - __esModule: true, - // We require some of the enums/types from the original module - ...originalModule, - useSafeAppsSDK: () => ({ - safe: { - safeAddress: "0x0000000000000000000000000000000000000002", - chainId: 5, - }, - sdk: undefined, - }), - } -}) - -jest.mock("src/utils/getWeb3Provider", () => ({ - getWeb3Provider: () => mockWeb3Provider, -})) - -const setupFetchStub = - (data: any, status: number = 200) => - (_url: string) => { - return Promise.resolve({ - json: () => Promise.resolve(data), - status, - ok: status === 200, - }) - } - -describe("useSafeTokenAllocation", () => { - const web3Provider = getWeb3Provider(undefined as never, undefined as never) - const mockCall = jest.fn() - web3Provider.call = mockCall - - afterEach(() => { - //@ts-ignore - global.fetch?.mockClear?.() - }) - - afterAll(() => { - // @ts-ignore - delete global.fetch - }) - - beforeEach(() => { - jest.resetAllMocks() - }) - - test("return 0 if no allocations / balances exist", async () => { - global.fetch = jest.fn().mockImplementation(setupFetchStub("", 404)) - const mockFetch = jest.spyOn(global, "fetch") - mockCall.mockImplementation((transaction: any, blockTag?: any) => { - const sigHash = keccak256(toUtf8Bytes("balanceOf(address)")).slice(0, 10) - if (transaction.data?.startsWith(sigHash)) { - return Promise.resolve("0x0") - } - return Promise.resolve("0x") - }) - - const { result } = renderHook(() => useSafeTokenAllocation()) - - await waitFor(() => { - expect(mockFetch).toHaveBeenCalled() - expect(result.current[0].votingPower?.toNumber()).toEqual(0) - expect(result.current[2]).toBeFalsy() - }) - }) - - test("return balance if no allocation exists", async () => { - global.fetch = jest.fn().mockImplementation(setupFetchStub("", 404)) - const mockFetch = jest.spyOn(global, "fetch") - - mockCall.mockImplementation((transaction: any, blockTag?: any) => { - const sigHash = keccak256(toUtf8Bytes("balanceOf(address)")).slice(0, 10) - if (transaction.data?.startsWith(sigHash)) { - return Promise.resolve(parseEther("100").toHexString()) - } - return Promise.resolve("0x") - }) - - const { result } = renderHook(() => useSafeTokenAllocation()) - - await waitFor(() => { - expect(mockFetch).toHaveBeenCalled() - expect(result.current[0].votingPower?.eq(parseEther("100"))).toBeTruthy() - expect(result.current[2]).toBeFalsy() - }) - }) - - test("always return allocation if it is rededeemed", async () => { - const mockAllocation = [ - { - tag: "user", - account: hexZeroPad("0x2", 20), - chainId: 1, - contract: hexZeroPad("0xabc", 20), - vestingId: hexZeroPad("0x4110", 32), - durationWeeks: 208, - startDate: 1657231200, - amount: "2000", - curve: 0, - proof: [], - }, - ] - - global.fetch = jest - .fn() - .mockImplementation(setupFetchStub(mockAllocation, 200)) - const mockFetch = jest.spyOn(global, "fetch") - - mockCall.mockImplementation((transaction: any, blockTag?: any) => { - const balanceOfSigHash = keccak256( - toUtf8Bytes("balanceOf(address)") - ).slice(0, 10) - const vestingsSigHash = keccak256(toUtf8Bytes("vestings(bytes32)")).slice( - 0, - 10 - ) - - if (transaction.data?.startsWith(balanceOfSigHash)) { - return Promise.resolve(parseEther("0").toHexString()) - } - if (transaction.data?.startsWith(vestingsSigHash)) { - return Promise.resolve( - defaultAbiCoder.encode( - [ - "address", - "uint8", - "bool", - "uint16", - "uint64", - "uint128", - "uint128", - "uint64", - "bool", - ], - [ - hexZeroPad("0x2", 20), - "0x1", - false, - 208, - 1657231200, - 2000, - 0, - 0, - false, - ] - ) - ) - } - return Promise.resolve("0x") - }) - - const { result } = renderHook(() => useSafeTokenAllocation()) - - await waitFor(() => { - expect(mockFetch).toHaveBeenCalled() - expect(result.current[0]?.votingPower.toNumber()).toEqual(2000) - expect(result.current[2]).toBeFalsy() - }) - }) - - test("ignore not redeemed allocations if deadline has passed", async () => { - const mockAllocation = [ - { - tag: "user", - account: hexZeroPad("0x2", 20), - chainId: 1, - contract: hexZeroPad("0xabc", 20), - vestingId: hexZeroPad("0x4110", 32), - durationWeeks: 208, - startDate: 1657231200, - amount: "2000", - curve: 0, - proof: [], - }, - ] - - global.fetch = jest - .fn() - .mockImplementation(setupFetchStub(mockAllocation, 200)) - const mockFetch = jest.spyOn(global, "fetch") - - mockCall.mockImplementation((transaction: any, blockTag?: any) => { - const balanceOfSigHash = keccak256( - toUtf8Bytes("balanceOf(address)") - ).slice(0, 10) - const vestingsSigHash = keccak256(toUtf8Bytes("vestings(bytes32)")).slice( - 0, - 10 - ) - const redeemDeadlineSigHash = keccak256( - toUtf8Bytes("redeemDeadline()") - ).slice(0, 10) - - if (transaction.data?.startsWith(balanceOfSigHash)) { - return Promise.resolve(parseEther("0").toHexString()) - } - if (transaction.data?.startsWith(vestingsSigHash)) { - return Promise.resolve( - defaultAbiCoder.encode( - [ - "address", - "uint8", - "bool", - "uint16", - "uint64", - "uint128", - "uint128", - "uint64", - "bool", - ], - [ZERO_ADDRESS, 0, false, 0, 0, 0, 0, 0, false] - ) - ) - } - if (transaction.data?.startsWith(redeemDeadlineSigHash)) { - // 30th Nov 2022 - return Promise.resolve(defaultAbiCoder.encode(["uint64"], [1669766400])) - } - return Promise.resolve("0x") - }) - - const { result } = renderHook(() => useSafeTokenAllocation()) - - await waitFor(() => { - expect(mockFetch).toHaveBeenCalled() - expect(result.current[0].votingPower.toNumber()).toEqual(0) - expect(result.current[2]).toBeFalsy() - }) - }) - - test("add not redeemed allocations if deadline has not passed", async () => { - const mockAllocation = [ - { - tag: "user", - account: hexZeroPad("0x2", 20), - chainId: 1, - contract: hexZeroPad("0xabc", 20), - vestingId: hexZeroPad("0x4110", 32), - durationWeeks: 208, - startDate: 1657231200, - amount: "2000", - curve: 0, - proof: [], - }, - ] - - global.fetch = jest - .fn() - .mockImplementation(setupFetchStub(mockAllocation, 200)) - const mockFetch = jest.spyOn(global, "fetch") - - mockCall.mockImplementation((transaction: any, blockTag?: any) => { - const balanceOfSigHash = keccak256( - toUtf8Bytes("balanceOf(address)") - ).slice(0, 10) - const vestingsSigHash = keccak256(toUtf8Bytes("vestings(bytes32)")).slice( - 0, - 10 - ) - const redeemDeadlineSigHash = keccak256( - toUtf8Bytes("redeemDeadline()") - ).slice(0, 10) - - if (transaction.data?.startsWith(balanceOfSigHash)) { - return Promise.resolve(parseEther("0").toHexString()) - } - if (transaction.data?.startsWith(vestingsSigHash)) { - return Promise.resolve( - defaultAbiCoder.encode( - [ - "address", - "uint8", - "bool", - "uint16", - "uint64", - "uint128", - "uint128", - "uint64", - "bool", - ], - [ZERO_ADDRESS, 0, false, 0, 0, 0, 0, 0, false] - ) - ) - } - if (transaction.data?.startsWith(redeemDeadlineSigHash)) { - // 08.Dec 2200 - return Promise.resolve(defaultAbiCoder.encode(["uint64"], [7287610110])) - } - return Promise.resolve("0x") - }) - - const { result } = renderHook(() => useSafeTokenAllocation()) - - await waitFor(() => { - expect(mockFetch).toHaveBeenCalled() - expect(result.current[0].votingPower?.toNumber()).toEqual(2000) - expect(result.current[2]).toBeFalsy() - }) - }) - - test("test formula: allocation - claimed + balance", async () => { - const mockAllocation = [ - { - tag: "user", - account: hexZeroPad("0x2", 20), - chainId: 1, - contract: hexZeroPad("0xabc", 20), - vestingId: hexZeroPad("0x4110", 32), - durationWeeks: 208, - startDate: 1657231200, - amount: "2000", - curve: 0, - proof: [], - }, - ] - - global.fetch = jest - .fn() - .mockImplementation(setupFetchStub(mockAllocation, 200)) - const mockFetch = jest.spyOn(global, "fetch") - - mockCall.mockImplementation((transaction: any, blockTag?: any) => { - const balanceOfSigHash = keccak256( - toUtf8Bytes("balanceOf(address)") - ).slice(0, 10) - const vestingsSigHash = keccak256(toUtf8Bytes("vestings(bytes32)")).slice( - 0, - 10 - ) - const redeemDeadlineSigHash = keccak256( - toUtf8Bytes("redeemDeadline()") - ).slice(0, 10) - - if (transaction.data?.startsWith(balanceOfSigHash)) { - return Promise.resolve(BigNumber.from("400").toHexString()) - } - if (transaction.data?.startsWith(vestingsSigHash)) { - return Promise.resolve( - defaultAbiCoder.encode( - [ - "address", - "uint8", - "bool", - "uint16", - "uint64", - "uint128", - "uint128", - "uint64", - "bool", - ], - // 1000 of 2000 tokens are claimed - [ - hexZeroPad("0x2", 20), - "0x1", - false, - 208, - 1657231200, - 2000, - 1000, - 0, - false, - ] - ) - ) - } - if (transaction.data?.startsWith(redeemDeadlineSigHash)) { - // 08.Dec 2200 - return Promise.resolve(defaultAbiCoder.encode(["uint64"], [7287610110])) - } - return Promise.resolve("0x") - }) - - const { result } = renderHook(() => useSafeTokenAllocation()) - - await waitFor(() => { - expect(mockFetch).toHaveBeenCalled() - expect(result.current[0].votingPower?.toNumber()).toEqual( - 2000 - 1000 + 400 - ) - expect(result.current[2]).toBeFalsy() - }) - }) - - test("test formula: allocation - claimed + balance, everything claimed and no balance", async () => { - const mockAllocation = [ - { - tag: "user", - account: hexZeroPad("0x2", 20), - chainId: 1, - contract: hexZeroPad("0xabc", 20), - vestingId: hexZeroPad("0x4110", 32), - durationWeeks: 208, - startDate: 1657231200, - amount: "2000", - curve: 0, - proof: [], - }, - ] - - global.fetch = jest - .fn() - .mockImplementation(setupFetchStub(mockAllocation, 200)) - const mockFetch = jest.spyOn(global, "fetch") - - mockCall.mockImplementation((transaction: any, blockTag?: any) => { - const balanceOfSigHash = keccak256( - toUtf8Bytes("balanceOf(address)") - ).slice(0, 10) - const vestingsSigHash = keccak256(toUtf8Bytes("vestings(bytes32)")).slice( - 0, - 10 - ) - const redeemDeadlineSigHash = keccak256( - toUtf8Bytes("redeemDeadline()") - ).slice(0, 10) - - if (transaction.data?.startsWith(balanceOfSigHash)) { - return Promise.resolve(BigNumber.from("0").toHexString()) - } - if (transaction.data?.startsWith(vestingsSigHash)) { - return Promise.resolve( - defaultAbiCoder.encode( - [ - "address", - "uint8", - "bool", - "uint16", - "uint64", - "uint128", - "uint128", - "uint64", - "bool", - ], - // 1000 of 2000 tokens are claimed - [ - hexZeroPad("0x2", 20), - "0x1", - false, - 208, - 1657231200, - 2000, - 2000, - 0, - false, - ] - ) - ) - } - if (transaction.data?.startsWith(redeemDeadlineSigHash)) { - // 08.Dec 2200 - return Promise.resolve(defaultAbiCoder.encode(["uint64"], [7287610110])) - } - return Promise.resolve("0x") - }) - - const { result } = renderHook(() => useSafeTokenAllocation()) - - await waitFor(() => { - expect(mockFetch).toHaveBeenCalled() - expect(result.current[0].votingPower?.toNumber()).toEqual(0) - expect(result.current[2]).toBeFalsy() - }) - }) -}) diff --git a/apps/safe-claiming-app/src/hooks/__tests__/useTokenBalance.test.ts b/apps/safe-claiming-app/src/hooks/__tests__/useTokenBalance.test.ts deleted file mode 100644 index eaa4f7bb1..000000000 --- a/apps/safe-claiming-app/src/hooks/__tests__/useTokenBalance.test.ts +++ /dev/null @@ -1,72 +0,0 @@ -import { waitFor } from "@testing-library/react" -import { renderHook } from "@testing-library/react-hooks" -import { BigNumber } from "ethers" -import { useTokenBalance } from "src/hooks/useTokenBalance" -import { SafeToken__factory } from "src/types/contracts/factories/SafeToken__factory" -import { getWeb3Provider } from "src/utils/getWeb3Provider" - -const mockWeb3Provider = { - _isProvider: true, - call: jest.fn(() => Promise.reject("call")), -} -jest.mock("@safe-global/safe-apps-react-sdk", () => { - const originalModule = jest.requireActual("@safe-global/safe-apps-react-sdk") - return { - __esModule: true, - // We require some of the enums/types from the original module - ...originalModule, - useSafeAppsSDK: () => ({ - safe: { - safeAddress: "0x6a13E0280740CC5bd35eeee33B470b5bBb93dF37", - chainId: 5, - }, - sdk: undefined, - }), - } -}) - -jest.mock("src/utils/getWeb3Provider", () => ({ - getWeb3Provider: () => mockWeb3Provider, -})) - -describe("useTokenBalance", () => { - const web3Provider = getWeb3Provider(undefined as never, undefined as never) - const mockCall = jest.fn() - web3Provider.call = mockCall - - afterAll(() => { - jest.unmock("src/utils/getWeb3Provider") - jest.unmock("@safe-global/safe-apps-react-sdk") - }) - - it("should return undefined initially", () => { - mockCall.mockImplementation(() => Promise.resolve) - const { result } = renderHook(() => useTokenBalance()) - expect(result.current[0]).toEqual(undefined) - expect(mockCall).toBeCalledTimes(0) - }) - - it("should return undefined on error", async () => { - mockCall.mockImplementation(() => Promise.reject("ERROR")) - const { result } = renderHook(() => useTokenBalance()) - await waitFor(() => { - expect(result.current[0]).toEqual(undefined) - expect(mockCall).toBeCalledTimes(1) - }) - }) - - it("should return 20.000 tokens", async () => { - mockCall.mockImplementation(async () => - Promise.resolve( - SafeToken__factory.createInterface().encodeFunctionResult("balanceOf", [ - BigNumber.from("20000"), - ]) - ) - ) - const { result } = renderHook(() => useTokenBalance()) - await waitFor(() => { - expect(result.current[0]).toEqual(BigNumber.from("20000")) - expect(mockCall).toBeCalledTimes(1) - }) - }) -}) diff --git a/apps/safe-claiming-app/src/hooks/useAmounts.ts b/apps/safe-claiming-app/src/hooks/useAmounts.ts deleted file mode 100644 index 39371a71f..000000000 --- a/apps/safe-claiming-app/src/hooks/useAmounts.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { BigNumber } from "ethers" -import { useEffect, useState } from "react" -import { calculateVestedAmount } from "src/utils/vesting" -import { Vesting } from "./useSafeTokenAllocation" - -export const useAmounts = (vestingClaim: Vesting | null): [string, string] => { - const [claimableAmount, setClaimableAmount] = useState("0") - const [amountInVesting, setAmountInVesting] = useState("0") - - useEffect(() => { - const refreshAmount = () => { - try { - if (!vestingClaim) { - return - } - const totalAmount = vestingClaim ? vestingClaim.amount : "0" - let vestedAmount = vestingClaim - ? calculateVestedAmount(vestingClaim) - : "0" - const amountClaimed = vestingClaim?.amountClaimed || "0" - - // If a user just claimed it can happen, that the amountClaimed is > vestedAmount for ~30s - if (BigNumber.from(vestedAmount).lt(amountClaimed)) { - vestedAmount = amountClaimed - } - - const newClaimableAmount = BigNumber.from(vestedAmount) - .sub(BigNumber.from(amountClaimed)) - .toString() - - const newAmountInVesting = BigNumber.from(totalAmount) - .sub(BigNumber.from(vestedAmount)) - .toString() - - setClaimableAmount(newClaimableAmount) - setAmountInVesting(newAmountInVesting) - } catch (error) { - // We ignore errors as we will retry it every 10 seconds anyway - console.error(error) - } - } - - if (!vestingClaim) { - return - } - - refreshAmount() - const refreshAmountInterval = window.setInterval(refreshAmount, 10000) - - return () => window.clearInterval(refreshAmountInterval) - }, [vestingClaim]) - - return [claimableAmount, amountInVesting] -} diff --git a/apps/safe-claiming-app/src/hooks/useDarkMode.ts b/apps/safe-claiming-app/src/hooks/useDarkMode.ts deleted file mode 100644 index f61ed1bcd..000000000 --- a/apps/safe-claiming-app/src/hooks/useDarkMode.ts +++ /dev/null @@ -1,38 +0,0 @@ -import { useEffect, useMemo, useState } from "react" -import initTheme from "src/config/theme" - -const isSystemDarkMode = (): boolean => { - if (typeof window === "undefined" || !window.matchMedia) return false - return window.matchMedia("(prefers-color-scheme: dark)").matches -} - -export const useDarkMode = (): boolean => { - const location = window.location - - const [isDarkMode, setIsDarkMode] = useState(false) - - useEffect(() => { - const isDark = location.hash.endsWith("+dark") - ? true - : location.hash.endsWith("+light") - ? false - : isSystemDarkMode() - - setIsDarkMode(isDark) - }, [location.hash]) - - return isDarkMode -} - -export const useLightDarkTheme = () => { - const isDarkMode = useDarkMode() - - useEffect(() => { - document.documentElement.setAttribute( - "data-theme", - isDarkMode ? "dark" : "light" - ) - }, [isDarkMode]) - - return useMemo(() => initTheme(isDarkMode), [isDarkMode]) -} diff --git a/apps/safe-claiming-app/src/hooks/useDelegate.ts b/apps/safe-claiming-app/src/hooks/useDelegate.ts deleted file mode 100644 index dd25392a6..000000000 --- a/apps/safe-claiming-app/src/hooks/useDelegate.ts +++ /dev/null @@ -1,58 +0,0 @@ -import { useSafeAppsSDK } from "@safe-global/safe-apps-react-sdk" -import { Contract, ethers } from "ethers" -import { - CHAIN_CONSTANTS, - DelegateRegistryAddress, - ZERO_ADDRESS, -} from "src/config/constants" -import { useEffect, useMemo, useState } from "react" -import { getWeb3Provider } from "src/utils/getWeb3Provider" -import { Interface } from "ethers/lib/utils" - -export const useDelegate = () => { - const [delegateAddress, setDelegateAddress] = useState() - const { sdk, safe } = useSafeAppsSDK() - - const ethersProvider = useMemo(() => getWeb3Provider(safe, sdk), [safe, sdk]) - - const chainConstants = CHAIN_CONSTANTS[safe.chainId] - - useEffect(() => { - if (!chainConstants) { - return - } - let isCurrent = true - - const delegateIDInBytes = ethers.utils.formatBytes32String( - chainConstants.DELEGATE_ID - ) - - const checkDelegate = async () => { - try { - const abiInterface = new Interface([ - "function delegation(address, bytes32) public view returns (address)", - "function setDelegate(bytes32 id, address delegate) public", - ]) - const address = await new Contract( - DelegateRegistryAddress, - abiInterface, - ethersProvider - ).delegation(safe.safeAddress, delegateIDInBytes) - - if (address !== ZERO_ADDRESS) { - isCurrent && setDelegateAddress(address) - } - } catch (error) { - console.error(error) - } - } - - checkDelegate() - - return () => { - isCurrent = false - } - }, [chainConstants, ethersProvider, safe.safeAddress]) - - return delegateAddress -} diff --git a/apps/safe-claiming-app/src/hooks/useDelegatesFile.ts b/apps/safe-claiming-app/src/hooks/useDelegatesFile.ts deleted file mode 100644 index 826460862..000000000 --- a/apps/safe-claiming-app/src/hooks/useDelegatesFile.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { useEffect, useState } from "react" -import { GUARDIANS_URL } from "src/config/constants" - -export type DelegateEntry = { - name?: string - address: string - ens?: string | null - image?: string | null - reason?: string - contribution?: string -} - -const shuffleArray = (array: T[]): T[] => - array.sort(() => Math.random() - 0.5) - -export const useDelegatesFile = (): [ - DelegateEntry[], - boolean, - string | undefined -] => { - const [delegates, setDelegates] = useState([]) - const [loading, setLoading] = useState(false) - const [error, setError] = useState() - - useEffect(() => { - let isMounted = true - - const parseFile = async () => { - try { - setLoading(true) - - const guardians = await fetch(GUARDIANS_URL).then((response) => { - if (!response.ok) { - throw Error(`Error fetching guardians: ${response.statusText}`) - } - return response.json() as Promise - }) - - isMounted && setDelegates(shuffleArray(guardians)) - } catch (err) { - console.error(err) - isMounted && setLoading(false) - isMounted && setError("Fetching delegates csv file failed.") - } - } - - parseFile() - - return () => { - isMounted = false - } - }, []) - return [delegates, loading, error] -} diff --git a/apps/safe-claiming-app/src/hooks/useEnsResolution.ts b/apps/safe-claiming-app/src/hooks/useEnsResolution.ts deleted file mode 100644 index 5a7761a37..000000000 --- a/apps/safe-claiming-app/src/hooks/useEnsResolution.ts +++ /dev/null @@ -1,104 +0,0 @@ -import { useSafeAppsSDK } from "@safe-global/safe-apps-react-sdk" -import { ethers } from "ethers" -import { useEffect, useMemo, useState } from "react" -import { parsePrefixedAddress, sameAddress } from "src/utils/addresses" -import { getWeb3Provider } from "src/utils/getWeb3Provider" - -type ENSResult = { - address: string - ens?: string -} - -export const useEnsResolution = ( - manualAddress: string, - debounce = true -): [ENSResult | undefined, string | undefined, boolean] => { - const [ensLoading, setEnsLoading] = useState(false) - const [ensResult, setEnsResult] = useState() - const [error, setError] = useState() - - const { safe, sdk } = useSafeAppsSDK() - const web3Provider = useMemo(() => getWeb3Provider(safe, sdk), [safe, sdk]) - - const chainPrefix = safe.chainId === 1 ? "eth" : "rin" - - useEffect(() => { - let isMounted = true - - if (manualAddress.length === 0) { - setEnsResult(undefined) - setEnsLoading(false) - setError(undefined) - return - } - - const { prefix, address: customAddress } = - parsePrefixedAddress(manualAddress) - - if (prefix && chainPrefix !== prefix) { - setEnsResult(undefined) - setEnsLoading(false) - setError(`The chain prefix needs to be ${chainPrefix}:`) - return - } - - if (ethers.utils.isAddress(customAddress)) { - const error = sameAddress(customAddress, safe.safeAddress) - ? "You can't delegate to your own Safe" - : undefined - // No need to resolve via ENS - setEnsResult({ address: ethers.utils.getAddress(customAddress) }) - setEnsLoading(false) - setError(error) - return - } - - const resolveAddress = async () => { - setEnsLoading(true) - - try { - const resolvedName = await web3Provider.resolveName(customAddress) - - if (resolvedName !== null && ethers.utils.isAddress(resolvedName)) { - if (sameAddress(resolvedName, safe.safeAddress)) { - isMounted && setEnsResult(undefined) - isMounted && setError("You can't delegate to your own Safe") - } else { - isMounted && - setEnsResult({ - address: resolvedName, - ens: customAddress, - }) - } - } else { - isMounted && setEnsResult(undefined) - isMounted && setError("Invalid address / ENS name") - } - } catch (err) { - console.error(err) - isMounted && setEnsResult(undefined) - isMounted && setError("Error while resolving ENS") - } - - isMounted && setEnsLoading(false) - } - - // reset error state - setError(undefined) - setEnsLoading(false) - let ensTimeout: number | undefined - if (debounce) { - ensTimeout = window.setTimeout(resolveAddress, 300) - } else { - resolveAddress() - } - - return () => { - window.clearTimeout(ensTimeout) - isMounted = false - } - // If we add the ensTimeout it will always trigger - }, [chainPrefix, debounce, manualAddress, safe.safeAddress, web3Provider]) - - return [ensResult, error, ensLoading] -} diff --git a/apps/safe-claiming-app/src/hooks/useIsTokenPaused.ts b/apps/safe-claiming-app/src/hooks/useIsTokenPaused.ts deleted file mode 100644 index 467876f10..000000000 --- a/apps/safe-claiming-app/src/hooks/useIsTokenPaused.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { useSafeAppsSDK } from "@safe-global/safe-apps-react-sdk" -import { useEffect, useState } from "react" -import { CHAIN_CONSTANTS } from "src/config/constants" -import { SafeToken__factory } from "src/types/contracts/factories/SafeToken__factory" -import { getWeb3Provider } from "src/utils/getWeb3Provider" - -/** - * Fetches if the token is currently paused from on-chain. - * If the fetching fails and initially we assume that the token is paused as the claimingViaModule should always work. - */ -export const useIsTokenPaused = () => { - const [isPaused, setIsPaused] = useState(true) - const { safe, sdk } = useSafeAppsSDK() - const web3Provider = getWeb3Provider(safe, sdk) - const chainConstants = CHAIN_CONSTANTS[safe.chainId] - - useEffect(() => { - let isMounted = true - - const fetchIsTokenPaused = async () => { - try { - const paused = await SafeToken__factory.connect( - chainConstants.SAFE_TOKEN_ADDRESS, - web3Provider - ).paused() - - isMounted && setIsPaused(paused) - } catch (error) { - console.error(error) - } - } - if (chainConstants) { - fetchIsTokenPaused() - } - return () => { - isMounted = false - } - }, [chainConstants, web3Provider]) - - return isPaused -} diff --git a/apps/safe-claiming-app/src/hooks/useLocalStorage.ts b/apps/safe-claiming-app/src/hooks/useLocalStorage.ts deleted file mode 100644 index 9a5abbabb..000000000 --- a/apps/safe-claiming-app/src/hooks/useLocalStorage.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { useSafeAppsSDK } from "@safe-global/safe-apps-react-sdk" -import { useMemo } from "react" -import Storage from "src/utils/storage/Storage" - -export const useLocalStorage = () => { - const { safe } = useSafeAppsSDK() - - const storage = useMemo( - () => new Storage(window.localStorage, safe.safeAddress), - [safe.safeAddress] - ) - - return storage -} diff --git a/apps/safe-claiming-app/src/hooks/useSafeSnapshot.ts b/apps/safe-claiming-app/src/hooks/useSafeSnapshot.ts deleted file mode 100644 index 0b074e898..000000000 --- a/apps/safe-claiming-app/src/hooks/useSafeSnapshot.ts +++ /dev/null @@ -1,85 +0,0 @@ -import useAsync, { type AsyncResult } from "src/hooks/useAsync" -import { useSafeSnapshotSpace } from "./useSnapshotSpace" - -type ShapshotProposalVars = { - space: string - first: number - skip: number - orderBy: "created" - orderDirection: "desc" | "asc" -} - -export type SnapshotProposal = { - id: string - title: string - state: "active" | "closed" | "pending" - author: string -} - -type GqlResponse = { - data: { - proposals: SnapshotProposal[] - } - errors?: Error[] -} - -const getSnapshot = async ( - variables: ShapshotProposalVars -): Promise => { - const SNAPSHOT_GQL_ENDPOINT = "https://hub.snapshot.org/graphql" - - const query = ` - query ($first: Int, $skip: Int, $space: String, $orderBy: String, $orderDirection: OrderDirection) { - proposals( - first: $first, - skip: $skip, - orderBy: $orderBy, - orderDirection: $orderDirection - where: { space_in: [$space] }, - ) { - id - title - state - author - } - } - ` - - const { data, errors } = (await fetch(SNAPSHOT_GQL_ENDPOINT, { - method: "POST", - headers: { - "Content-Type": "application/json", - }, - body: JSON.stringify({ - query, - variables, - }), - }).then((res) => res.json())) as GqlResponse - - // GraphQL returns an array of errors in res.errors - if (errors) { - throw errors[0] - } - - return data.proposals -} - -const getSafeSnapshot = ( - amount: number, - space: string -): Promise => { - return getSnapshot({ - space, - first: amount, - skip: 0, - orderBy: "created", - orderDirection: "desc", - }) -} - -const useSafeSnapshot = (amount: number): AsyncResult => { - const snapshotSpace = useSafeSnapshotSpace() - return useAsync(() => getSafeSnapshot(amount, snapshotSpace), [snapshotSpace]) -} - -export default useSafeSnapshot diff --git a/apps/safe-claiming-app/src/hooks/useSafeTokenAllocation.ts b/apps/safe-claiming-app/src/hooks/useSafeTokenAllocation.ts deleted file mode 100644 index 68f915e5f..000000000 --- a/apps/safe-claiming-app/src/hooks/useSafeTokenAllocation.ts +++ /dev/null @@ -1,190 +0,0 @@ -import { Provider } from "@ethersproject/providers" -import { useSafeAppsSDK } from "@safe-global/safe-apps-react-sdk" -import { isPast } from "date-fns" -import { BigNumber } from "ethers" -import { defaultAbiCoder, Interface } from "ethers/lib/utils" -import { useMemo } from "react" -import { CHAIN_CONSTANTS, ZERO_ADDRESS } from "src/config/constants" -import { getWeb3Provider } from "src/utils/getWeb3Provider" -import useAsync from "./useAsync" - -export const VESTING_URL = - "https://safe-claiming-app-data.gnosis-safe.io/allocations/" - -type VestingData = { - tag: "user" | "ecosystem" | "investor" - account: string - chainId: number - contract: string - vestingId: string - durationWeeks: number - startDate: number - amount: string - curve: 0 | 1 - proof: string[] -} - -export type Vesting = VestingData & { - isExpired: boolean - isRedeemed: boolean - amountClaimed: string -} - -// We currently do not have typechain as dependency so we fallback to human readable ABIs -const airdropInterface = new Interface([ - "function redeemDeadline() public returns (uint64)", - "function vestings(bytes32) public returns ({address account, uint8 curveType,bool managed, uint16 durationWeeks, uint64 startDate, uint128 amount, uint128 amountClaimed, uint64 pausingDate,bool cancelled})", -]) -const tokenInterface = new Interface([ - "function balanceOf(address _owner) public view returns (uint256 balance)", -]) - -/** - * Add on-chain information to allocation. - * Fetches if the redeem deadline is expired and the claimed tokens from on-chain - */ -const completeAllocation = async ( - allocation: VestingData, - web3Provider: Provider -): Promise => { - const onChainVestingData = await web3Provider.call({ - to: allocation.contract, - data: airdropInterface.encodeFunctionData("vestings", [ - allocation.vestingId, - ]), - }) - - const decodedVestingData = defaultAbiCoder.decode( - // account, curveType, managed, durationWeeks, startDate, amount, amountClaimed, pausingDate, cancelled} - [ - "address", - "uint8", - "bool", - "uint16", - "uint64", - "uint128", - "uint128", - "uint64", - "bool", - ], - onChainVestingData - ) - const isRedeemed = - decodedVestingData[0].toLowerCase() !== ZERO_ADDRESS.toLowerCase() - if (isRedeemed) { - return { - ...allocation, - isRedeemed, - isExpired: false, - amountClaimed: decodedVestingData[6], - } - } - - // Allocation is not yet redeemed => check the redeemDeadline - const redeemDeadline = await web3Provider.call({ - to: allocation.contract, - data: airdropInterface.encodeFunctionData("redeemDeadline"), - }) - - const redeemDeadlineDate = new Date( - BigNumber.from(redeemDeadline).mul(1000).toNumber() - ) - - // Allocation is valid if redeem deadline is in future - return { - ...allocation, - isRedeemed, - isExpired: isPast(redeemDeadlineDate), - amountClaimed: "0", - } -} - -const fetchAllocation = async ( - chainId: number, - safeAddress: string -): Promise => { - try { - const response = await fetch(`${VESTING_URL}${chainId}/${safeAddress}.json`) - - // No file exists => the safe is not part of any vesting - if (response.status === 404) { - return Promise.resolve([]) as Promise - } - - // Some other error - if (!response.ok) { - throw Error(`Error fetching vestings: ${response.statusText}`) - } - - // Success - return response.json() as Promise - } catch (err) { - throw Error(`Error fetching vestings: ${err}`) - } -} - -const fetchTokenBalance = async ( - chainId: number, - safeAddress: string, - web3Provider: Provider -): Promise => { - try { - const safeTokenAddress = CHAIN_CONSTANTS[chainId].SAFE_TOKEN_ADDRESS - if (!safeTokenAddress) return "0" - - return await web3Provider.call({ - to: safeTokenAddress, - data: tokenInterface.encodeFunctionData("balanceOf", [safeAddress]), - }) - } catch (err) { - throw Error(`Error fetching Safe token balance: ${err}`) - } -} - -const computeVotingPower = (allocationData: Vesting[], balance: string) => { - const tokensInVesting = allocationData.reduce( - (acc, data) => - data.isExpired ? acc : acc.add(data.amount).sub(data.amountClaimed), - BigNumber.from(0) - ) - - // add balance - return tokensInVesting.add(balance || "0") -} -/** - * Fetches allocated tokens and combines it with the on-chain status of the vesting. - */ -const useSafeTokenAllocation = (): [ - { votingPower: BigNumber; vestingData: Vesting[] } | undefined, - Error | undefined, - boolean -] => { - const { safe, sdk } = useSafeAppsSDK() - const web3Provider = useMemo(() => getWeb3Provider(safe, sdk), [safe, sdk]) - - const chainId = safe.chainId - - return useAsync< - { votingPower: BigNumber; vestingData: Vesting[] } | undefined - >(async () => { - const vestingData = await Promise.all( - await fetchAllocation(safe.chainId, safe.safeAddress).then( - (allocations) => - allocations.map((allocation) => - completeAllocation(allocation, web3Provider) - ) - ) - ) - const balance = await fetchTokenBalance( - safe.chainId, - safe.safeAddress, - web3Provider - ) - - const votingPower = computeVotingPower(vestingData, balance) - - return { votingPower, vestingData } - }, [chainId, safe.safeAddress, safe.chainId]) -} - -export default useSafeTokenAllocation diff --git a/apps/safe-claiming-app/src/hooks/useSnapshotSpace.ts b/apps/safe-claiming-app/src/hooks/useSnapshotSpace.ts deleted file mode 100644 index b15a3866f..000000000 --- a/apps/safe-claiming-app/src/hooks/useSnapshotSpace.ts +++ /dev/null @@ -1,7 +0,0 @@ -import { useSafeAppsSDK } from "@safe-global/safe-apps-react-sdk" -import { CHAIN_CONSTANTS } from "src/config/constants" - -export const useSafeSnapshotSpace = () => { - const { safe } = useSafeAppsSDK() - return CHAIN_CONSTANTS[safe.chainId]?.DELEGATE_ID -} diff --git a/apps/safe-claiming-app/src/hooks/useTokenBalance.ts b/apps/safe-claiming-app/src/hooks/useTokenBalance.ts deleted file mode 100644 index 569a659ad..000000000 --- a/apps/safe-claiming-app/src/hooks/useTokenBalance.ts +++ /dev/null @@ -1,36 +0,0 @@ -import { useSafeAppsSDK } from "@safe-global/safe-apps-react-sdk" -import { BigNumber, ethers } from "ethers" -import { useMemo } from "react" -import { CHAIN_CONSTANTS } from "src/config/constants" -import useAsync, { type AsyncResult } from "src/hooks/useAsync" -import { SafeToken__factory } from "src/types/contracts/factories/SafeToken__factory" -import { getWeb3Provider } from "src/utils/getWeb3Provider" - -const getSafeBalance = ( - tokenAddress: string, - provider: ethers.providers.Provider, - safeAddress: string -): Promise => { - return SafeToken__factory.connect(tokenAddress, provider).balanceOf( - safeAddress - ) -} - -/** - * Fetches the current token balance. - */ -export const useTokenBalance = (): AsyncResult => { - const { safe, sdk } = useSafeAppsSDK() - const web3Provider = useMemo(() => getWeb3Provider(safe, sdk), [safe, sdk]) - const chainConstants = CHAIN_CONSTANTS[safe.chainId] - - return useAsync( - () => - getSafeBalance( - chainConstants.SAFE_TOKEN_ADDRESS, - web3Provider, - safe.safeAddress - ), - [safe.chainId, web3Provider, safe.safeAddress] - ) -} diff --git a/apps/safe-claiming-app/src/index.tsx b/apps/safe-claiming-app/src/index.tsx deleted file mode 100644 index c903b05fb..000000000 --- a/apps/safe-claiming-app/src/index.tsx +++ /dev/null @@ -1,15 +0,0 @@ -import React from "react" -import { GlobalStyles } from "@mui/material" - -import { render } from "react-dom" -import { AppSwitch } from "./components/AppSwitch" - -const container = document.getElementById("root") as HTMLDivElement - -render( - - - - , - container -) diff --git a/apps/safe-claiming-app/src/react-app-env.d.ts b/apps/safe-claiming-app/src/react-app-env.d.ts deleted file mode 100644 index 6431bc5fc..000000000 --- a/apps/safe-claiming-app/src/react-app-env.d.ts +++ /dev/null @@ -1 +0,0 @@ -/// diff --git a/apps/safe-claiming-app/src/setupTests.ts b/apps/safe-claiming-app/src/setupTests.ts deleted file mode 100644 index 6a0fd1230..000000000 --- a/apps/safe-claiming-app/src/setupTests.ts +++ /dev/null @@ -1,5 +0,0 @@ -// jest-dom adds custom jest matchers for asserting on DOM nodes. -// allows you to do things like: -// expect(element).toHaveTextContent(/react/i) -// learn more: https://github.com/testing-library/jest-dom -import "@testing-library/jest-dom" diff --git a/apps/safe-claiming-app/src/types/vestings.ts b/apps/safe-claiming-app/src/types/vestings.ts deleted file mode 100644 index 073963092..000000000 --- a/apps/safe-claiming-app/src/types/vestings.ts +++ /dev/null @@ -1,18 +0,0 @@ -export type VestingData = { - tag: "user" | "ecosystem" | "investor" - account: string - chainId: number - contract: string - vestingId: string - durationWeeks: number - startDate: number - amount: string - curve: 0 | 1 - proof: string[] -} -export type VestingStatus = { - isRedeemed: boolean - amountClaimed: string -} - -export type VestingClaim = VestingData & VestingStatus diff --git a/apps/safe-claiming-app/src/utils/__tests__/createClaimAndDelegateTxs.test.ts b/apps/safe-claiming-app/src/utils/__tests__/createClaimAndDelegateTxs.test.ts deleted file mode 100644 index 4092c5386..000000000 --- a/apps/safe-claiming-app/src/utils/__tests__/createClaimAndDelegateTxs.test.ts +++ /dev/null @@ -1,229 +0,0 @@ -import { ethers } from "ethers" -import { parseBytes32String } from "ethers/lib/utils" -import { AppState } from "src/App" -import { Airdrop__factory } from "src/types/contracts" -import { createClaimAndDelegateTxs } from "../contracts/createClaimAndDelegateTxs" -import { delegateRegistryInterface } from "../contracts/delegateRegistry" - -describe("createClaimAndDelegateTxs", () => { - const mockUserAirdropAddress = ethers.utils.hexZeroPad("0x2", 20) - const mockInvestorVestingAddress = ethers.utils.hexZeroPad("0x4", 20) - - it("only delegate", () => { - const safeAddress = ethers.utils.hexZeroPad("0x5afe", 20) - const delegateAddress = ethers.utils.hexZeroPad("0x1", 20) - const appState: AppState = { - delegateData: [], - vestingData: [ - { - isExpired: false, - account: safeAddress, - amount: ethers.utils.parseEther("100").toString(), - amountClaimed: "0", - chainId: 5, - contract: mockUserAirdropAddress, - curve: 0, - durationWeeks: 416, - isRedeemed: false, - proof: [ - "0x4697528f2cd5e98bce29be252b25ed33b79d8f0245bb7a3d0f00bb32e50128bb", - ], - startDate: 10000, - tag: "user", - vestingId: - "0xabfe3d0bfb3df17a4aa39d6967f722ff82c765601417a4957434023c97d5b111", - }, - ], - isTokenPaused: true, - claimedAmount: "0", - delegate: { address: delegateAddress }, - delegateAddressFromContract: undefined, - } - const txs = createClaimAndDelegateTxs({ - appState, - amount: "0", - chainId: 5, - safeAddress, - investorClaimable: "0", - isMaxAmountSelected: false, - userClaimable: ethers.utils.parseEther("100").toString(), - }) - - expect(txs).toHaveLength(1) - const decodedTx = delegateRegistryInterface.decodeFunctionData( - "setDelegate", - txs[0].data - ) - expect(parseBytes32String(decodedTx[0])).toEqual("tutis.eth") - expect(decodedTx[1]).toEqual(delegateAddress) - }) - - it("redeem + claim user airdrop while paused", () => { - const safeAddress = ethers.utils.hexZeroPad("0x5afe", 20) - const delegateAddress = ethers.utils.hexZeroPad("0x1", 20) - const appState: AppState = { - delegateData: [], - vestingData: [ - { - isExpired: false, - account: safeAddress, - amount: ethers.utils.parseEther("100").toString(), - amountClaimed: "0", - chainId: 5, - contract: mockUserAirdropAddress, - curve: 0, - durationWeeks: 416, - isRedeemed: false, - proof: [ - "0x4697528f2cd5e98bce29be252b25ed33b79d8f0245bb7a3d0f00bb32e50128bb", - ], - startDate: 10000, - tag: "user", - vestingId: - "0xabfe3d0bfb3df17a4aa39d6967f722ff82c765601417a4957434023c97d5b111", - }, - ], - isTokenPaused: true, - claimedAmount: "0", - delegate: { address: delegateAddress }, - delegateAddressFromContract: delegateAddress, - } - const txs = createClaimAndDelegateTxs({ - appState, - amount: "100", - chainId: 5, - safeAddress, - investorClaimable: "0", - isMaxAmountSelected: false, - userClaimable: ethers.utils.parseEther("100").toString(), - }) - - expect(txs).toHaveLength(2) - const airdropInterface = Airdrop__factory.createInterface() - const decodedRedeemTx = airdropInterface.decodeFunctionData( - "redeem", - txs[0].data - ) - const decodedClaimTx = airdropInterface.decodeFunctionData( - "claimVestedTokensViaModule", - txs[1].data - ) - /* - uint8 curveType, - uint16 durationWeeks, - uint64 startDate, - uint128 amount, - bytes32[] calldata proof - */ - expect(decodedRedeemTx[0]).toEqual(0) - expect(decodedRedeemTx[1]).toEqual(416) - expect(decodedRedeemTx[2].toString()).toEqual("10000") - expect(decodedRedeemTx[3].toString()).toEqual( - ethers.utils.parseEther("100").toString() - ) - - expect(decodedClaimTx[1].toString().toLowerCase()).toEqual(safeAddress) // beneficiary - expect(decodedClaimTx[2].toString()).toEqual( - ethers.utils.parseEther("100").toString() - ) // amount - - // check to address - expect(txs[0].to.toLowerCase()).toEqual(mockUserAirdropAddress) - expect(txs[1].to.toLowerCase()).toEqual(mockUserAirdropAddress) - }) - - it("do not claim investor airdrop while paused", () => { - const safeAddress = ethers.utils.hexZeroPad("0x5afe", 20) - const delegateAddress = ethers.utils.hexZeroPad("0x1", 20) - const appState: AppState = { - delegateData: [], - vestingData: [ - { - isExpired: false, - account: safeAddress, - amount: ethers.utils.parseEther("100").toString(), - amountClaimed: "0", - chainId: 5, - contract: mockInvestorVestingAddress, - curve: 0, - durationWeeks: 416, - isRedeemed: false, - proof: [ - "0x4697528f2cd5e98bce29be252b25ed33b79d8f0245bb7a3d0f00bb32e50128bb", - ], - startDate: 10000, - tag: "investor", - vestingId: - "0xabfe3d0bfb3df17a4aa39d6967f722ff82c765601417a4957434023c97d5b111", - }, - ], - isTokenPaused: true, - claimedAmount: "0", - delegate: { address: delegateAddress }, - delegateAddressFromContract: delegateAddress, - } - const txs = createClaimAndDelegateTxs({ - appState, - amount: "100", - chainId: 5, - safeAddress, - investorClaimable: ethers.utils.parseEther("100").toString(), - isMaxAmountSelected: false, - userClaimable: "0", - }) - - expect(txs).toHaveLength(0) - }) - - it("claim investor airdrop if unpaused", () => { - const safeAddress = ethers.utils.hexZeroPad("0x5afe", 20) - const delegateAddress = ethers.utils.hexZeroPad("0x1", 20) - const appState: AppState = { - delegateData: [], - vestingData: [ - { - isExpired: false, - account: safeAddress, - amount: ethers.utils.parseEther("100").toString(), - amountClaimed: "0", - chainId: 5, - contract: mockInvestorVestingAddress, - curve: 0, - durationWeeks: 416, - isRedeemed: true, - proof: [ - "0x4697528f2cd5e98bce29be252b25ed33b79d8f0245bb7a3d0f00bb32e50128bb", - ], - startDate: 10000, - tag: "investor", - vestingId: - "0xabfe3d0bfb3df17a4aa39d6967f722ff82c765601417a4957434023c97d5b111", - }, - ], - isTokenPaused: false, - claimedAmount: "0", - delegate: { address: delegateAddress }, - delegateAddressFromContract: delegateAddress, - } - const txs = createClaimAndDelegateTxs({ - appState, - amount: "100", - chainId: 5, - safeAddress, - investorClaimable: ethers.utils.parseEther("100").toString(), - isMaxAmountSelected: false, - userClaimable: "0", - }) - - expect(txs).toHaveLength(1) - const airdropInterface = Airdrop__factory.createInterface() - const decodedClaimTx = airdropInterface.decodeFunctionData( - "claimVestedTokens", - txs[0].data - ) - expect(decodedClaimTx[1].toString().toLowerCase()).toEqual(safeAddress) // beneficiary - expect(decodedClaimTx[2].toString()).toEqual( - ethers.utils.parseEther("100").toString() - ) // amount - }) -}) diff --git a/apps/safe-claiming-app/src/utils/__tests__/format.test.ts b/apps/safe-claiming-app/src/utils/__tests__/format.test.ts deleted file mode 100644 index 93249819b..000000000 --- a/apps/safe-claiming-app/src/utils/__tests__/format.test.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { formatAmount, shortenAddress } from "../format" - -describe("format", () => { - describe("formatAmount", () => { - it("should format to a fixed amount of decimals", () => { - expect(formatAmount(0, 0)).toEqual("0") - expect(formatAmount(0, 2)).toEqual("0.00") - expect(formatAmount(0, 5)).toEqual("0.00000") - - expect(formatAmount(1.11111111111111, 0)).toEqual("1") - expect(formatAmount(1.11111111111111, 2)).toEqual("1.11") - expect(formatAmount(1.11111111111111, 5)).toEqual("1.11111") - }) - - it("should split at thousands", () => { - expect(formatAmount(1_000, 0)).toEqual("1,000") - expect(formatAmount(10_000, 0)).toEqual("10,000") - expect(formatAmount(1_000_000, 0)).toEqual("1,000,000") - }) - }) - describe("shortenAddress", () => { - it("should shorten an address", () => { - expect( - shortenAddress("0x1234567890123456789012345678901234567890") - ).toEqual("0x1234...7890") - }) - - it("should shorten an address with custom length", () => { - expect( - shortenAddress("0x1234567890123456789012345678901234567890", 5) - ).toEqual("0x12345...67890") - }) - }) -}) diff --git a/apps/safe-claiming-app/src/utils/__tests__/splitAirdropAmounts.test.ts b/apps/safe-claiming-app/src/utils/__tests__/splitAirdropAmounts.test.ts deleted file mode 100644 index 74aaecb88..000000000 --- a/apps/safe-claiming-app/src/utils/__tests__/splitAirdropAmounts.test.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { ethers } from "ethers" -import { MAX_UINT128 } from "src/config/constants" -import { splitAirdropAmounts } from "../splitAirdropAmounts" - -describe("splitAirdropAmounts", () => { - it("should always claim max uint128 if max is selected", () => { - const [userAmount, investorAmount, ecosystemAmount] = splitAirdropAmounts( - true, - "2000.0", - ethers.utils.parseEther("1000").toString(), - "0" - ) - expect(userAmount).toEqual(MAX_UINT128.toString()) - expect(investorAmount).toEqual(MAX_UINT128.toString()) - expect(ecosystemAmount).toEqual(MAX_UINT128.toString()) - }) - - it("should only claim from user airdrop if amount <= userClaim", () => { - { - const [userAmount, investorAmount, ecosystemAmount] = splitAirdropAmounts( - false, - "1.0", - ethers.utils.parseEther("1000").toString(), - "0" - ) - expect(userAmount).toEqual(ethers.utils.parseEther("1").toString()) - expect(ecosystemAmount).toEqual("0") - } - { - const [userAmount, investorAmount, ecosystemAmount] = splitAirdropAmounts( - false, - "1000.0", - ethers.utils.parseEther("1000").toString(), - "0" - ) - expect(userAmount).toEqual(ethers.utils.parseEther("1000").toString()) - expect(investorAmount).toEqual("0") - expect(ecosystemAmount).toEqual("0") - } - }) - - it("should claim from ecosystem airdrop if amount > userClaim", () => { - { - const [userAmount, investorAmount, ecosystemAmount] = splitAirdropAmounts( - false, - "1000.00001", - ethers.utils.parseEther("1000").toString(), - "0" - ) - expect(userAmount).toEqual(ethers.utils.parseEther("1000").toString()) - expect(investorAmount).toEqual("0") - expect(ecosystemAmount).toEqual( - ethers.utils.parseEther("0.00001").toString() - ) - } - { - const [userAmount, investorAmount, ecosystemAmount] = splitAirdropAmounts( - false, - "2000", - ethers.utils.parseEther("1000").toString(), - "0" - ) - expect(userAmount).toEqual(ethers.utils.parseEther("1000").toString()) - expect(investorAmount).toEqual("0") - expect(ecosystemAmount).toEqual( - ethers.utils.parseEther("1000").toString() - ) - } - }) - - it("should claim from investor airdrop if amount >= investor allocation", () => { - { - const [userAmount, investorAmount, ecosystemAmount] = splitAirdropAmounts( - false, - "1000", - "0", - ethers.utils.parseEther("1000").toString() - ) - expect(userAmount).toEqual("0") - expect(investorAmount).toEqual(ethers.utils.parseEther("1000").toString()) - expect(ecosystemAmount).toEqual("0") - } - { - const [userAmount, investorAmount, ecosystemAmount] = splitAirdropAmounts( - false, - "0.002", - "0", - ethers.utils.parseEther("1000").toString() - ) - expect(userAmount).toEqual("0") - expect(investorAmount).toEqual( - ethers.utils.parseEther("0.002").toString() - ) - expect(ecosystemAmount).toEqual("0") - } - }) - - it("should claim from user, investor and ecosystem airdrop if amount > user + investor", () => { - { - const [userAmount, investorAmount, ecosystemAmount] = splitAirdropAmounts( - false, - "2000", - ethers.utils.parseEther("500").toString(), - ethers.utils.parseEther("1000").toString() - ) - expect(userAmount).toEqual(ethers.utils.parseEther("500").toString()) - expect(investorAmount).toEqual(ethers.utils.parseEther("1000").toString()) - expect(ecosystemAmount).toEqual(ethers.utils.parseEther("500").toString()) - } - }) -}) diff --git a/apps/safe-claiming-app/src/utils/__tests__/validation.test.ts b/apps/safe-claiming-app/src/utils/__tests__/validation.test.ts deleted file mode 100644 index a1b35b075..000000000 --- a/apps/safe-claiming-app/src/utils/__tests__/validation.test.ts +++ /dev/null @@ -1,100 +0,0 @@ -import { maxDecimals, minMaxValue, mustBeFloat } from "../validation" - -describe("validation", () => { - describe("mustBeFloat", () => { - it("successfully validates integers", () => { - expect(mustBeFloat("2")).toBeUndefined() - expect(mustBeFloat("-0992")).toBeUndefined() - expect(mustBeFloat("000001")).toBeUndefined() - expect(mustBeFloat("-24")).toBeUndefined() - expect(mustBeFloat("9999999999999")).toBeUndefined() - }) - - it("successfully validates floats", () => { - expect(mustBeFloat("2.0001")).toBeUndefined() - expect(mustBeFloat("-0000.992")).toBeUndefined() - expect(mustBeFloat("000001.0242")).toBeUndefined() - expect(mustBeFloat("-24.24")).toBeUndefined() - expect(mustBeFloat("9999999999999.99999999999")).toBeUndefined() - }) - - it("invalidates non numbers", () => { - expect(mustBeFloat("2.000whoopsie")).toBeDefined() - expect(mustBeFloat("$&/(=)//`´#'+*~")).toBeDefined() - }) - }) - describe("minMaxValue", () => { - describe("with number boundaries", () => { - it("successfuly validation with integers", () => { - expect(minMaxValue(0, 100, "0")).toBeUndefined() - expect(minMaxValue(0, 100, "1")).toBeUndefined() - expect(minMaxValue(0, 100, "23")).toBeUndefined() - expect(minMaxValue(0, 100, "99")).toBeUndefined() - expect(minMaxValue(0, 100, "100")).toBeUndefined() - }) - - it("failed validation with integers", () => { - expect(minMaxValue(0, 100, "-1")).toBeDefined() - expect(minMaxValue(0, 100, "101")).toBeDefined() - }) - - it("successfuly validation with floats", () => { - expect(minMaxValue(0, 100, "0.00001")).toBeUndefined() - expect(minMaxValue(0, 100, "23.92424")).toBeUndefined() - expect(minMaxValue(0, 100, "99.9999999")).toBeUndefined() - expect(minMaxValue(0, 100, "100.0")).toBeUndefined() - }) - - it("failed validation with floats", () => { - expect(minMaxValue(0, 100, "-1.991")).toBeDefined() - expect(minMaxValue(0, 100, "-0.0001")).toBeDefined() - expect(minMaxValue(0, 100, "100.000000000001")).toBeDefined() - }) - }) - - describe("with string boundaries", () => { - it("successfuly validation with integers", () => { - expect(minMaxValue("0", "100", "1")).toBeUndefined() - expect(minMaxValue("0", "100", "23")).toBeUndefined() - expect(minMaxValue("0", "100", "99")).toBeUndefined() - expect(minMaxValue("0", "100", "100")).toBeUndefined() - }) - - it("failed validation with integers", () => { - expect(minMaxValue("0", "100", "-1")).toBeDefined() - expect(minMaxValue("0", "100", "101")).toBeDefined() - }) - - it("successfuly validation with floats", () => { - expect(minMaxValue("0", "100", "0.00001")).toBeUndefined() - expect(minMaxValue("0", "100", "23.92424")).toBeUndefined() - expect(minMaxValue("0", "100", "99.9999999")).toBeUndefined() - expect(minMaxValue("0", "100", "100.0")).toBeUndefined() - }) - - it("failed validation with floats", () => { - expect(minMaxValue("0", "100", "-1.991")).toBeDefined() - expect(minMaxValue("0", "100", "100.000000000001")).toBeDefined() - }) - }) - }) - describe("maxDecimals", () => { - it("no decimals are valid", () => { - expect(maxDecimals("0", 4)).toBeUndefined() - expect(maxDecimals("-20", 4)).toBeUndefined() - expect(maxDecimals("1", 4)).toBeUndefined() - expect(maxDecimals("9999999999999999999999", 4)).toBeUndefined() - }) - - it("less or equal decimals are valid", () => { - expect(maxDecimals("0.1", 4)).toBeUndefined() - expect(maxDecimals("0.123", 4)).toBeUndefined() - expect(maxDecimals("0.1234", 4)).toBeUndefined() - }) - - it("too many decimals are invalid", () => { - expect(maxDecimals("0.12345", 4)).toBeDefined() - expect(maxDecimals("0.123456789101112131415", 4)).toBeDefined() - }) - }) -}) diff --git a/apps/safe-claiming-app/src/utils/addresses.ts b/apps/safe-claiming-app/src/utils/addresses.ts deleted file mode 100644 index cec80195a..000000000 --- a/apps/safe-claiming-app/src/utils/addresses.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { getAddress, isAddress } from "ethers/lib/utils" - -export type PrefixedAddress = { - prefix?: string - address: string -} - -export const sameAddress = (address1: string, address2: string) => { - return address1.toLowerCase() === address2.toLowerCase() -} - -export const parsePrefixedAddress = (value: string): PrefixedAddress => { - let [prefix, address] = value.split(":") - - if (!address) { - address = value - prefix = "" - } - - return { - prefix: prefix || undefined, - address: isAddress(address) ? getAddress(address) : value, - } -} diff --git a/apps/safe-claiming-app/src/utils/contracts/airdrop.ts b/apps/safe-claiming-app/src/utils/contracts/airdrop.ts deleted file mode 100644 index 20f94ce69..000000000 --- a/apps/safe-claiming-app/src/utils/contracts/airdrop.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { Airdrop__factory } from "src/types/contracts/factories/Airdrop__factory" -import { VestingClaim } from "src/types/vestings" - -const createRedeemTx = (vestingClaim: VestingClaim, airdropAddress: string) => { - const airdropInterface = Airdrop__factory.createInterface() - const redeemTxData = airdropInterface.encodeFunctionData("redeem", [ - vestingClaim.curve, - vestingClaim.durationWeeks, - vestingClaim.startDate, - vestingClaim.amount.toString(), - vestingClaim.proof, - ]) - - return { - to: airdropAddress, - value: "0", - data: redeemTxData, - } -} - -const createClaimTx = ( - vestingClaim: VestingClaim, - amount: string, - safeAddress: string, - airdropAddress: string, - isTokenPaused: boolean -) => { - const airdropInterface = Airdrop__factory.createInterface() - let claimData - if (isTokenPaused) { - claimData = airdropInterface.encodeFunctionData( - "claimVestedTokensViaModule", - [vestingClaim.vestingId, safeAddress, amount] - ) - } else { - claimData = airdropInterface.encodeFunctionData("claimVestedTokens", [ - vestingClaim.vestingId, - safeAddress, - amount, - ]) - } - - return { - to: airdropAddress, - value: "0", - data: claimData, - } -} - -export const createAirdropTxs = ( - vestingClaim: VestingClaim, - amount: string, - safeAddress: string, - airdropAddress: string, - isTokenPaused: boolean -) => { - const txs: { to: string; value: string; data: string }[] = [] - // add redeem function if claiming for the first time - if (!vestingClaim.isRedeemed) { - const redeemTx = createRedeemTx(vestingClaim, airdropAddress) - txs.push(redeemTx) - } - - // add claim function - const claimTx = createClaimTx( - vestingClaim, - amount, - safeAddress, - airdropAddress, - isTokenPaused - ) - txs.push(claimTx) - - return txs -} diff --git a/apps/safe-claiming-app/src/utils/contracts/createClaimAndDelegateTxs.ts b/apps/safe-claiming-app/src/utils/contracts/createClaimAndDelegateTxs.ts deleted file mode 100644 index 4ef4d26bc..000000000 --- a/apps/safe-claiming-app/src/utils/contracts/createClaimAndDelegateTxs.ts +++ /dev/null @@ -1,92 +0,0 @@ -import { BigNumber } from "ethers" -import { AppState } from "src/App" -import { createAirdropTxs } from "src/utils/contracts/airdrop" -import { createDelegateTx } from "src/utils/contracts/delegateRegistry" -import { splitAirdropAmounts } from "src/utils/splitAirdropAmounts" -import { sameAddress } from "../addresses" - -export const createClaimAndDelegateTxs = ({ - appState, - chainId, - safeAddress, - isMaxAmountSelected, - amount, - userClaimable, - investorClaimable, -}: { - appState: AppState - chainId: number - safeAddress: string - isMaxAmountSelected: boolean - amount: string - userClaimable: string - investorClaimable: string -}) => { - const txs: { to: string; value: string; data: string }[] = [] - const { delegate, delegateAddressFromContract, vestingData, isTokenPaused } = - appState - if (!delegate?.address) return txs - - // Add delegate tx if necessary - if ( - !delegateAddressFromContract || - !sameAddress(delegate.address, delegateAddressFromContract) - ) { - const delegateTx = createDelegateTx(delegate.address, chainId) - txs.push(delegateTx) - } - - // Create tx for userAirdrop - const [userAmount, investorAmount, ecosystemAmount] = splitAirdropAmounts( - isMaxAmountSelected, - amount, - userClaimable, - investorClaimable - ) - - const userClaim = - vestingData.find((vesting) => vesting.tag === "user") ?? null - const ecosystemClaim = - vestingData.find((vesting) => vesting.tag === "ecosystem") ?? null - const investorClaim = - vestingData.find((vesting) => vesting.tag === "investor") ?? null - - if (userClaim && BigNumber.from(userAmount).gt(0)) { - txs.push( - ...createAirdropTxs( - userClaim, - userAmount, - safeAddress, - userClaim.contract, - isTokenPaused - ) - ) - } - - if (ecosystemClaim && BigNumber.from(ecosystemAmount).gt(0)) { - txs.push( - ...createAirdropTxs( - ecosystemClaim, - ecosystemAmount, - safeAddress, - ecosystemClaim.contract, - isTokenPaused - ) - ) - } - if (investorClaim && BigNumber.from(investorAmount).gt(0)) { - // Investors use the VestingPool contract and can not claim if paused - if (!isTokenPaused) { - txs.push( - ...createAirdropTxs( - investorClaim, - investorAmount, - safeAddress, - investorClaim.contract, - false - ) - ) - } - } - return txs -} diff --git a/apps/safe-claiming-app/src/utils/contracts/delegateRegistry.ts b/apps/safe-claiming-app/src/utils/contracts/delegateRegistry.ts deleted file mode 100644 index a3e024703..000000000 --- a/apps/safe-claiming-app/src/utils/contracts/delegateRegistry.ts +++ /dev/null @@ -1,26 +0,0 @@ -import { ethers } from "ethers" -import { Interface } from "ethers/lib/utils" -import { CHAIN_CONSTANTS, DelegateRegistryAddress } from "src/config/constants" - -export const delegateRegistryInterface = new Interface([ - "function delegation(bytes32, bytes32) public view returns (address)", - "function setDelegate(bytes32 id, address delegate) public", -]) -export const createDelegateTx = (delegateAddress: string, chainId: number) => { - const chainConstants = CHAIN_CONSTANTS[chainId] - - // Add delegate tx if necessary - const delegateId = ethers.utils.formatBytes32String( - chainConstants.DELEGATE_ID - ) - const delegateData = delegateRegistryInterface.encodeFunctionData( - "setDelegate", - [delegateId, delegateAddress] - ) - - return { - to: DelegateRegistryAddress, - value: "0", - data: delegateData, - } -} diff --git a/apps/safe-claiming-app/src/utils/format.ts b/apps/safe-claiming-app/src/utils/format.ts deleted file mode 100644 index 310c22330..000000000 --- a/apps/safe-claiming-app/src/utils/format.ts +++ /dev/null @@ -1,23 +0,0 @@ -export const formatAmount = (amount: number, decimals: number) => { - const formatter = new Intl.NumberFormat(undefined, { - style: "decimal", - minimumFractionDigits: decimals, - maximumFractionDigits: decimals, - }) - return formatter.format(amount) -} - -export const shortenAddress = (address: string, length = 4): string => { - return `${address.slice(0, length + 2)}...${address.slice(-length)}` -} - -export const getExplorerURL = (address: string, chainId: string) => { - switch (chainId) { - case "1": - return `https://etherscan.io/address/${address}` - case "4": - return `https://rinkeby.etherscan.io/address/${address}` - default: - return undefined - } -} diff --git a/apps/safe-claiming-app/src/utils/getWeb3Provider.ts b/apps/safe-claiming-app/src/utils/getWeb3Provider.ts deleted file mode 100644 index c91ac2d21..000000000 --- a/apps/safe-claiming-app/src/utils/getWeb3Provider.ts +++ /dev/null @@ -1,11 +0,0 @@ -import SafeAppsSDK, { SafeInfo } from "@safe-global/safe-apps-sdk" -import { SafeAppProvider } from "@safe-global/safe-apps-provider" -import { ethers } from "ethers" - -export const getWeb3Provider = ( - safe: SafeInfo, - sdk: SafeAppsSDK -): ethers.providers.Provider => { - const safeAppProvider = new SafeAppProvider(safe, sdk) - return new ethers.providers.Web3Provider(safeAppProvider) -} diff --git a/apps/safe-claiming-app/src/utils/merkleProof.ts b/apps/safe-claiming-app/src/utils/merkleProof.ts deleted file mode 100644 index 1530566c7..000000000 --- a/apps/safe-claiming-app/src/utils/merkleProof.ts +++ /dev/null @@ -1,54 +0,0 @@ -import { ethers, BigNumber } from "ethers" - -export const EMPTY_HASH = ethers.utils.keccak256(ethers.utils.hexlify("0x")) - -export const combineAndHash = (leaf1: string, leaf2: string): string => { - const combined = ethers.utils.defaultAbiCoder.encode( - ["bytes32", "bytes32"], - [leaf1, leaf2] - ) - return ethers.utils.keccak256(combined) -} - -const generate = ( - input: string[], - element?: string -): { root: string; proof: string[] } => { - const proof: string[] = [] - const elements = [...input] - let count = elements.length - while (count > 1) { - for (let i = 0; i < count; i += 2) { - const leaf1 = elements[i] - const leaf2 = i + 1 >= count ? EMPTY_HASH : elements[i + 1] - if (leaf1 === element) { - proof.push(leaf2) - elements[i / 2] = element - } else if (leaf2 === element) { - proof.push(leaf1) - elements[i / 2] = element - } else { - if (BigNumber.from(leaf1).lt(BigNumber.from(leaf2))) { - elements[i / 2] = combineAndHash(leaf1, leaf2) - } else { - elements[i / 2] = combineAndHash(leaf2, leaf1) - } - } - } - count = Math.ceil(count / 2) - } - return { proof, root: elements[0] } -} - -export const generateRoot = (elements: string[]): string => { - const { root } = generate(elements) - return root -} - -export const generateProof = ( - elements: string[], - element: string -): string[] => { - const { proof } = generate(elements, element) - return proof -} diff --git a/apps/safe-claiming-app/src/utils/splitAirdropAmounts.ts b/apps/safe-claiming-app/src/utils/splitAirdropAmounts.ts deleted file mode 100644 index 3fa871b6b..000000000 --- a/apps/safe-claiming-app/src/utils/splitAirdropAmounts.ts +++ /dev/null @@ -1,40 +0,0 @@ -import { ethers, BigNumber } from "ethers" -import { MAX_UINT128 } from "src/config/constants" - -/** - * Splits the amount the user wants to claim into user airdrop and ecosystem airdrop. - * - * @returns userAmount in Wei, investorAmount in Wei, ecosystemAmount in Wei - */ -export const splitAirdropAmounts = ( - isMaxAmountSelected: boolean, - amount: string, - userAirdropClaimable: string, - investorClaimable: string -): [string, string, string] => { - if (isMaxAmountSelected) { - return [ - MAX_UINT128.toString(), - MAX_UINT128.toString(), - MAX_UINT128.toString(), - ] - } - - const amountInWei = ethers.utils.parseEther(amount) - if (amountInWei.gt(BigNumber.from(userAirdropClaimable))) { - const leftOver = amountInWei.sub(BigNumber.from(userAirdropClaimable)) - if (leftOver.gt(BigNumber.from(investorClaimable))) { - // We claim full user and investor airdrop + part leftOver of ecosystem - return [ - userAirdropClaimable, - investorClaimable, - leftOver.sub(BigNumber.from(investorClaimable)).toString(), - ] - } else { - // We claim full user + leftOver of investor airdrop - return [userAirdropClaimable, leftOver.toString(), "0"] - } - } - // We just claim the user airdrop - return [amountInWei.toString(), "0", "0"] -} diff --git a/apps/safe-claiming-app/src/utils/storage/Storage.ts b/apps/safe-claiming-app/src/utils/storage/Storage.ts deleted file mode 100644 index dd3911d10..000000000 --- a/apps/safe-claiming-app/src/utils/storage/Storage.ts +++ /dev/null @@ -1,61 +0,0 @@ -import { STORAGE_PREFIX } from "src/config/constants" - -type BrowserStorage = typeof localStorage | typeof sessionStorage - -class Storage { - private prefix: string - private storage: BrowserStorage - private safeAddress: string - - constructor( - storage: BrowserStorage, - safeAddress: string, - prefix = STORAGE_PREFIX - ) { - this.prefix = prefix - this.storage = storage - this.safeAddress = safeAddress - } - - private prefixKey = (key: string): string => { - return `${this.prefix}${this.safeAddress}${key}` - } - - public getItem = (key: string): T | undefined => { - const fullKey = this.prefixKey(key) - let saved: string | null = null - try { - saved = this.storage.getItem(fullKey) - } catch { - // ignore - } - - if (!saved || saved === "undefined") return - - try { - return JSON.parse(saved) as T - } catch { - // ignore - } - } - - public setItem = (key: string, item: T): void => { - const fullKey = this.prefixKey(key) - try { - this.storage.setItem(fullKey, JSON.stringify(item)) - } catch { - // ignore - } - } - - public removeItem = (key: string): void => { - const fullKey = this.prefixKey(key) - try { - this.storage.removeItem(fullKey) - } catch { - // ignore - } - } -} - -export default Storage diff --git a/apps/safe-claiming-app/src/utils/validation.ts b/apps/safe-claiming-app/src/utils/validation.ts deleted file mode 100644 index c77de3992..000000000 --- a/apps/safe-claiming-app/src/utils/validation.ts +++ /dev/null @@ -1,22 +0,0 @@ -export const mustBeFloat = (value: string): string | undefined => - value && Number.isNaN(Number(value)) ? "Must be a number" : undefined - -export const minMaxValue = ( - min: number | string, - max: number | string, - value: string -) => { - if ( - Number.parseFloat(value) >= Number(min) && - Number.parseFloat(value) <= Number(max) - ) { - return undefined - } - - return `Must be between ${min} and ${max}` -} - -export const maxDecimals = (value: string, max: number) => - value.split(".")[1]?.length > max - ? `Precision too high. Only ${max} allowed` - : undefined diff --git a/apps/safe-claiming-app/src/utils/vesting.ts b/apps/safe-claiming-app/src/utils/vesting.ts deleted file mode 100644 index ff9bf8e71..000000000 --- a/apps/safe-claiming-app/src/utils/vesting.ts +++ /dev/null @@ -1,41 +0,0 @@ -import { BigNumber } from "ethers" -import { Vesting } from "src/hooks/useSafeTokenAllocation" - -const LINEAR_CURVE = 0 -const EXPONENTIAL_CURVE = 1 - -/* - * This buffer is needed as the block timestamp is slightly behind the real timestamp. - * Event when using the latest block timestamp the gas estimation of created txs sometimes fails. - * Experiments showed that 30 seconds is a solid value. - */ -export const DESYNC_BUFFER = 30 - -export const calculateVestedAmount = (vestingClaim: Vesting): string => { - const durationInSeconds = vestingClaim.durationWeeks * 7 * 24 * 60 * 60 - const timeStampInSeconds = - Math.floor(new Date().getTime() / 1000) - DESYNC_BUFFER - - // Vesting did not start yet! - if (timeStampInSeconds < vestingClaim.startDate) { - return "0" - } - const vestedSeconds = timeStampInSeconds - vestingClaim.startDate - - if (vestedSeconds >= durationInSeconds) { - return vestingClaim.amount.toString() - } - if (vestingClaim.curve === LINEAR_CURVE) { - return BigNumber.from(vestingClaim.amount) - .mul(BigNumber.from(vestedSeconds)) - .div(BigNumber.from(durationInSeconds)) - .toString() - } - if (vestingClaim.curve === EXPONENTIAL_CURVE) { - return BigNumber.from(vestingClaim.amount) - .mul(BigNumber.from(vestedSeconds).pow(2)) - .div(BigNumber.from(durationInSeconds).pow(2)) - .toString() - } - throw new Error("Invalid curve type") -} diff --git a/apps/safe-claiming-app/src/widgets/ClaimingWidget.tsx b/apps/safe-claiming-app/src/widgets/ClaimingWidget.tsx deleted file mode 100644 index 91ded1961..000000000 --- a/apps/safe-claiming-app/src/widgets/ClaimingWidget.tsx +++ /dev/null @@ -1,242 +0,0 @@ -import { useSafeAppsSDK } from "@safe-global/safe-apps-react-sdk" -import { OpenInNewRounded } from "@mui/icons-material" -import { - Box, - Button, - type ButtonProps, - Typography, - type TypographyProps, - Link, - Skeleton, - Card, - styled, -} from "@mui/material" -import { BigNumber, ethers } from "ethers" -import { useMemo } from "react" -import { ReactComponent as SafeIcon } from "src/assets/images/safe-token.svg" -import { SelectedDelegate } from "src/components/steps/Claim/SelectedDelegate" -import { - CLAIMING_APP_URL, - DISCORD_URL, - FORUM_URL, - WEB_APP_URL, -} from "src/config/constants" -import { useDelegate } from "src/hooks/useDelegate" -import { useDelegatesFile } from "src/hooks/useDelegatesFile" -import useSafeTokenAllocation from "src/hooks/useSafeTokenAllocation" -import { sameAddress } from "src/utils/addresses" -import { formatAmount } from "src/utils/format" -import { SpaceContent } from "src/widgets/styles" - -const Title = (props: TypographyProps) => ( - - {props.children} - -) - -const Subtitle = (props: TypographyProps) => ( - - {props.children} - -) - -const StyledExternalLink = styled(Link)` - display: inline-flex; - align-items: center; - font-weight: 700; - gap: 4px; - text-decoration: none; -` - -const ExternalLink = ({ url, label }: { url: string; label: string }) => { - return ( - - {label} - - - ) -} - -const WIDGET_WIDTH = "300px" - -const StyledButton = (props: ButtonProps) => ( - -) - -const WIDGET_HEIGHT = 300 - -const ClaimingWidget = () => { - const [delegates] = useDelegatesFile() - const delegateAddressFromContract = useDelegate() - const { safe } = useSafeAppsSDK() - - const currentDelegate = useMemo(() => { - if (delegateAddressFromContract) { - const registeredDelegateFromData = delegates.find((entry) => - sameAddress(entry.address, delegateAddressFromContract) - ) - return ( - registeredDelegateFromData || { address: delegateAddressFromContract } - ) - } - }, [delegateAddressFromContract, delegates]) - - const [safeTokenAllocation, , loading] = useSafeTokenAllocation() - const { vestingData, votingPower } = safeTokenAllocation ?? {} - - const totalClaimed = vestingData?.reduce( - (prev, current) => prev.add(current.amountClaimed), - BigNumber.from(0) - ) - - const unredeemedAllocations = vestingData?.some( - (vesting) => !vesting.isRedeemed - ) - - const currentChainPrefix = safe.chainId === 1 ? "eth" : "gor" - const claimingSafeAppUrl = `${WEB_APP_URL}/apps?safe=${currentChainPrefix}:${safe.safeAddress}&appUrl=${CLAIMING_APP_URL}` - - const ctaWidget = ( - <> -
- Become part of Safe's future -
- - Help us unlock ownership for everyone by joining the discussions on - the and our{" "} - . - -
- - ) - - const votingPowerWidget = ( - <> -
- Your voting power - -
- {totalClaimed?.gt(0) ? ( - <> - - You've already claimed{" "} - {formatAmount(Number(ethers.utils.formatEther(totalClaimed)), 2)}{" "} - SAFE - - {currentDelegate && ( - - - Delegated to - - - {}} - /> - - - )} - - ) : ( - <> - {unredeemedAllocations && ( - - You have unredeemed tokens. Claim any amount before the 27th of - December or the tokens will be transferred back into the SafeDAO - treasury. - - )} - - Claim and delegate - - - )} - - ) - - if (loading) { - return ( - - - - ) - } - - return ( - - - {votingPower && votingPower.eq(0) ? ctaWidget : votingPowerWidget} - - - ) -} - -export default ClaimingWidget diff --git a/apps/safe-claiming-app/src/widgets/SnapshotWidget.tsx b/apps/safe-claiming-app/src/widgets/SnapshotWidget.tsx deleted file mode 100644 index d10629587..000000000 --- a/apps/safe-claiming-app/src/widgets/SnapshotWidget.tsx +++ /dev/null @@ -1,206 +0,0 @@ -import { OpenInNewRounded } from "@mui/icons-material" -import { - Box, - Chip, - Link, - Typography, - Skeleton, - styled, - Card, -} from "@mui/material" -import { FORUM_URL } from "src/config/constants" -import useSafeSnapshot, { - type SnapshotProposal, -} from "src/hooks/useSafeSnapshot" -import { useSafeSnapshotSpace } from "src/hooks/useSnapshotSpace" -import { SpaceContent } from "src/widgets/styles" -import palette from "../config/colors" - -const SNAPSHOT_STATE_COLORS: Record = { - active: "success.main", - pending: "border.main", - closed: "#743EE4", -} - -export const _getProposalNumber = (title: string): string => { - // Find anything that matches "SEP #n" - const SEP_REGEX = /SEP\s#\d+/g - return title.match(SEP_REGEX)?.[0] || "" -} - -export const _getProposalTitle = (title: string): string => { - // Find anything after "] " or ": " - const TITLE_REGEX = /(\]|:) (.*)/ - return title.match(TITLE_REGEX)?.at(-1) || "" -} - -const Proposal = styled("a")` - height: 47px; - width: 100%; - display: grid; - grid-gap: 4px; - align-items: center; - text-decoration: none; - padding: 0px 16px; - border: 1px solid; - border-radius: 6px; - grid-template-columns: auto minmax(auto, 9fr) 1fr 1fr; - grid-template-areas: "number title title title title title title title title title status link"; -` - -const StyledChip = styled(Chip)` - border-radius: 20px; - min-width: 76px; - max-width: 76px; - text-align: center; - height: 23px; - font-weight: bold; -` - -const StyledNumber = styled(Box)` - height: 18px; - font-size: 13px; - border-radius: 6px; - padding: 0px 6px; - white-space: nowrap; - margin-right: 12px; -` - -const StyledExternalLink = styled(Link)` - display: flex; - align-items: center; - font-weight: 700; - gap: 8px; - text-decoration: none; -` - -const SnapshotProposals = ({ - proposals, - snapshotLink, -}: { - proposals: SnapshotProposal[] - snapshotLink: string -}) => ( - <> - {proposals?.map((proposal) => ( - - - {_getProposalNumber(proposal.title)} - - - - {_getProposalTitle(proposal.title)} - - - - - - - - ))} - -) - -const SnapshotWidget = () => { - const snapshotSpace = useSafeSnapshotSpace() - - const SNAPSHOT_LINK = `https://snapshot.org/#/${snapshotSpace}` - const PROPOSAL_AMOUNT = 3 - - const [proposals, loading] = useSafeSnapshot(PROPOSAL_AMOUNT) - - return ( - - -
- - Latest proposals - - - {loading || !proposals ? ( - Array.from(Array(PROPOSAL_AMOUNT).keys()).map((key) => ( - - )) - ) : ( - - )} - -
- - - View all - - - SafeDAO Forum - - -
-
- ) -} - -export default SnapshotWidget diff --git a/apps/safe-claiming-app/src/widgets/Widget.tsx b/apps/safe-claiming-app/src/widgets/Widget.tsx deleted file mode 100644 index d4eaf9275..000000000 --- a/apps/safe-claiming-app/src/widgets/Widget.tsx +++ /dev/null @@ -1,24 +0,0 @@ -import { Box, styled } from "@mui/material" -import ClaimingWidget from "src/widgets/ClaimingWidget" -import SnapshotWidget from "src/widgets/SnapshotWidget" - -const WidgetsWrapper = styled(Box)` - display: flex; - flex-direction: column; - gap: 24px; - - @media (min-width: 600px) { - flex-direction: row; - } -` - -const Widget = () => { - return ( - - - - - ) -} - -export default Widget diff --git a/apps/safe-claiming-app/src/widgets/__tests__/index.test.ts b/apps/safe-claiming-app/src/widgets/__tests__/index.test.ts deleted file mode 100644 index cc70bc694..000000000 --- a/apps/safe-claiming-app/src/widgets/__tests__/index.test.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { _getProposalNumber, _getProposalTitle } from "../SnapshotWidget" - -describe("SnapshotWidget", () => { - describe("getProposalNumber", () => { - it("should return proposal numbers from differently formatted titles", () => { - const proposalNumber = _getProposalNumber( - "SEP #1: SafeDAO Participation Agreement" - ) - expect(proposalNumber).toBe("SEP #1") - - const proposalNumber2 = _getProposalNumber( - "[SEP #2] Community Initiative To Unpause Token Contract (Enabling Transferability)" - ) - expect(proposalNumber2).toBe("SEP #2") - }) - }) - describe("getProposalTitle", () => { - it("should strip differently formatted proposal numbers", () => { - const proposalNumber = _getProposalTitle( - "SEP #1: SafeDAO Participation Agreement" - ) - expect(proposalNumber).toBe("SafeDAO Participation Agreement") - - const proposalNumber2 = _getProposalTitle( - "[SEP #2] Community Initiative To Unpause Token Contract (Enabling Transferability)" - ) - expect(proposalNumber2).toBe( - "Community Initiative To Unpause Token Contract (Enabling Transferability)" - ) - }) - }) -}) diff --git a/apps/safe-claiming-app/src/widgets/styles.tsx b/apps/safe-claiming-app/src/widgets/styles.tsx deleted file mode 100644 index 9706221f9..000000000 --- a/apps/safe-claiming-app/src/widgets/styles.tsx +++ /dev/null @@ -1,14 +0,0 @@ -import { Box, type BoxProps } from "@mui/material" - -export const SpaceContent = (props: BoxProps) => { - return ( - - ) -} diff --git a/apps/safe-claiming-app/tsconfig.json b/apps/safe-claiming-app/tsconfig.json deleted file mode 100644 index 02fc51b1a..000000000 --- a/apps/safe-claiming-app/tsconfig.json +++ /dev/null @@ -1,24 +0,0 @@ -{ - "compilerOptions": { - "baseUrl": ".", - "target": "es5", - "lib": ["dom", "dom.iterable", "esnext"], - "allowJs": true, - "skipLibCheck": true, - "esModuleInterop": true, - "allowSyntheticDefaultImports": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "noFallthroughCasesInSwitch": true, - "module": "esnext", - "moduleResolution": "node", - "resolveJsonModule": true, - "isolatedModules": true, - "noEmit": true, - "jsx": "react-jsx" - }, - "include": ["./src"], - "paths": { - "src/*": ["./*"] - } -} diff --git a/apps/tx-builder/CHANGELOG.md b/apps/tx-builder/CHANGELOG.md index d87975146..693667b8a 100644 --- a/apps/tx-builder/CHANGELOG.md +++ b/apps/tx-builder/CHANGELOG.md @@ -2,6 +2,146 @@ This file was generated using [@jscutlery/semver](https://github.com/jscutlery/semver). +## [1.18.3](https://github.com/safe-global/safe-react-apps/compare/tx-builder-1.18.2...tx-builder-1.18.3) (2025-09-25) + + + +## [1.18.2](https://github.com/safe-global/safe-react-apps/compare/tx-builder-1.18.1...tx-builder-1.18.2) (2025-08-28) + + +### Bug Fixes + +* error: Can't resolve 'process/browser' in axios ([#860](https://github.com/safe-global/safe-react-apps/issues/860)) ([354a4b7](https://github.com/safe-global/safe-react-apps/commit/354a4b725ca4cb27d6935eed20022ee721719cd1)) +* **tx-builder:** update help article link ([#849](https://github.com/safe-global/safe-react-apps/issues/849)) ([2bbc198](https://github.com/safe-global/safe-react-apps/commit/2bbc198c090c81eb784f4a8a646382520e338056)) + + + +## [1.18.1](https://github.com/safe-global/safe-react-apps/compare/tx-builder-1.18.0...tx-builder-1.18.1) (2025-01-22) + + + +# [1.18.0](https://github.com/safe-global/safe-react-apps/compare/tx-builder-1.17.1...tx-builder-1.18.0) (2024-10-22) + + +### Bug Fixes + +* **empty-library-screen:** adjust empty library screen to be according to figmadesign ([d2c865b](https://github.com/safe-global/safe-react-apps/commit/d2c865b151e69b3d08fd7f1a82cd7b2fff22fadb)) +* **icons-alginment:** adjust general icons colors and screens alignment ([009b9c2](https://github.com/safe-global/safe-react-apps/commit/009b9c2857a4e6144f348b98d7c105fffcf0e4e9)) +* **library-icon-color:** change icon color in your transactions library page ([5fd16ae](https://github.com/safe-global/safe-react-apps/commit/5fd16aef8f952aee438b2218456c21958f3398d3)) +* **remove-unused-code:** remove stick parameter definition from the TransactionSectionWrapper ([e35bf4c](https://github.com/safe-global/safe-react-apps/commit/e35bf4ca18c938018c6c1539f2a81f608dc5bde2)) +* **safe-deployments-version:** update safe-deployments version in order to fix issues on Linea ([ffdbbb7](https://github.com/safe-global/safe-react-apps/commit/ffdbbb7a39adc0c320cdd3708659f537b3052730)) +* **tx-builder-alignment:** put the TxBuilder form aligned on top ([067bd9d](https://github.com/safe-global/safe-react-apps/commit/067bd9dd5142f1516432b0dc4975d3bd800bbbae)) +* **tx-builder-colors:** adjust transaction library colors across all theme modes ([8e60f03](https://github.com/safe-global/safe-react-apps/commit/8e60f031266707403d3747fc16c6945a3c80c57d)) +* **tx-builder-dark-mode:** remove hard coded colors values ([a812f11](https://github.com/safe-global/safe-react-apps/commit/a812f1107620d84db969e89856701902eda7f437)) +* **tx-builder-tooltips:** fix tooltip background on generic modal ([5b6014c](https://github.com/safe-global/safe-react-apps/commit/5b6014c20467fabe0f42e9b59c2264ced6a6ffa8)) +* **tx-builder-upload:** change upload file text color ([7611fa5](https://github.com/safe-global/safe-react-apps/commit/7611fa54fed89b350f6270f335213dfa13e7c06e)) +* **tx-builder:** remove unused icons and components ([fc937ba](https://github.com/safe-global/safe-react-apps/commit/fc937bae6eca7977d1f1b3e29414328e6620b012)) +* unit tests ([8878fc2](https://github.com/safe-global/safe-react-apps/commit/8878fc26ffba2a3fa2375ca553e1cb6de684693e)) + + +### Features + +* **dark-mode:** add dark mode theme support in the theme provider ([bc238d6](https://github.com/safe-global/safe-react-apps/commit/bc238d62868df8bec7f6bb40864289156aed9942)) +* **dark-mode:** adjust components to support dark mode ([8f85da9](https://github.com/safe-global/safe-react-apps/commit/8f85da9c3afbaf3cce6eb86413ef4f1cd4475591)) +* **mode-in-context:** add theme mode in the context in ([ef7df39](https://github.com/safe-global/safe-react-apps/commit/ef7df3941c7f3362efa38e36edbaf7f0db75b6b0)) +* **replace-icons:** replace old images the correct ones from figma ([9220ac1](https://github.com/safe-global/safe-react-apps/commit/9220ac1ac9f1a5c4647e74d8da3f20dfe75b2bb9)) +* **tx-builder-components:** move necessary components from react-component library to tx-builder ([a8d98d6](https://github.com/safe-global/safe-react-apps/commit/a8d98d693594bc52019accfcfa05f14986ad1444)) +* **tx-builder-theme:** update tx-builder theme ([6c43cbd](https://github.com/safe-global/safe-react-apps/commit/6c43cbd2ff69f32f1a3776161ec320d7ddd0fa80)) +* **tx-builder:** move necessary icons to transaction builder ([8e83db8](https://github.com/safe-global/safe-react-apps/commit/8e83db885328ce09a2d05017daf3f2b098a29a82)) + + + +## [1.17.1](https://github.com/safe-global/safe-react-apps/compare/tx-builder-1.17.0...tx-builder-1.17.1) (2024-08-28) + + +### Bug Fixes + +* **tx-builder:** fix contract method filtering ([#816](https://github.com/safe-global/safe-react-apps/issues/816)) ([9cbefc5](https://github.com/safe-global/safe-react-apps/commit/9cbefc5b2daa4a897641115eafa7fe7d14faeb64)) + + + +# [1.17.0](https://github.com/safe-global/safe-react-apps/compare/tx-builder-1.16.5...tx-builder-1.17.0) (2024-08-20) + + +### Features + +* **tx builder:** use cgw for simulation configs ([#801](https://github.com/safe-global/safe-react-apps/issues/801)) ([cbf139e](https://github.com/safe-global/safe-react-apps/commit/cbf139e48a64fe79dbf11dc5aab17982142e1060)) +* **tx-builder:** autocomplete for contract method ([#809](https://github.com/safe-global/safe-react-apps/issues/809)) ([012f1f7](https://github.com/safe-global/safe-react-apps/commit/012f1f73988f82cd2f1784e2b6b062ee30b20b5e)) + + + +# [1.17.0](https://github.com/safe-global/safe-react-apps/compare/tx-builder-1.16.5...tx-builder-1.17.0) (2024-04-18) + + +### Features + +* **tx builder:** use cgw for simulation configs ([#801](https://github.com/safe-global/safe-react-apps/issues/801)) ([cbf139e](https://github.com/safe-global/safe-react-apps/commit/cbf139e48a64fe79dbf11dc5aab17982142e1060)) + + + +## [1.16.5](https://github.com/safe-global/safe-react-apps/compare/tx-builder-1.16.4...tx-builder-1.16.5) (2024-01-25) + + + +## [1.16.5](https://github.com/safe-global/safe-react-apps/compare/tx-builder-1.16.4...tx-builder-1.16.5) (2023-11-29) + + + +## [1.16.4](https://github.com/safe-global/safe-react-apps/compare/tx-builder-1.16.3...tx-builder-1.16.4) (2023-09-22) + + + +## [1.16.3](https://github.com/safe-global/safe-react-apps/compare/tx-builder-1.16.2...tx-builder-1.16.3) (2023-08-23) + + + +## [1.16.2](https://github.com/safe-global/safe-react-apps/compare/tx-builder-1.16.1...tx-builder-1.16.2) (2023-07-20) + + +### Bug Fixes + +* **tx-builder:** disable simulation on base ([#741](https://github.com/safe-global/safe-react-apps/issues/741)) ([a1d7a63](https://github.com/safe-global/safe-react-apps/commit/a1d7a6399aad4d1fe144ad470135e4196103ec76)) + + + +## [1.16.1](https://github.com/safe-global/safe-react-apps/compare/tx-builder-1.16.0...tx-builder-1.16.1) (2023-06-20) + + +### Bug Fixes + +* **tx-builder:** Migration Dialog ([#713](https://github.com/safe-global/safe-react-apps/issues/713)) ([cb6bdd4](https://github.com/safe-global/safe-react-apps/commit/cb6bdd453ef3e9ccb1c06eb469b84a402ec7d29c)) + + + +# [1.16.0](https://github.com/safe-global/safe-react-apps/compare/tx-builder-1.15.0...tx-builder-1.16.0) (2023-06-20) + + +### Bug Fixes + +* **tx-builder:** Open a window instead download batch for Safari and Firefox ([#707](https://github.com/safe-global/safe-react-apps/issues/707)) ([718cc53](https://github.com/safe-global/safe-react-apps/commit/718cc5316bbc383394c9f12635b748b97fc09c05)) +* **tx-builder:** Open required app in new window ([#708](https://github.com/safe-global/safe-react-apps/issues/708)) ([b4bfc2c](https://github.com/safe-global/safe-react-apps/commit/b4bfc2ca955d64964ce7de8ee4f145aebc04e09e)) + + +### Features + +* **tx-builder:** deployment in a new domain ([#698](https://github.com/safe-global/safe-react-apps/issues/698)) ([9cb1d59](https://github.com/safe-global/safe-react-apps/commit/9cb1d593acaafedaacfd9f11ac8384aeb37e3fe2)) + + + +# [1.15.0](https://github.com/safe-global/safe-react-apps/compare/tx-builder-1.14.0...tx-builder-1.15.0) (2023-05-11) + + +### Bug Fixes + +* **tx-builder,wallet-connect:** update help centre link ([c724a85](https://github.com/safe-global/safe-react-apps/commit/c724a8566e81bc895c0fa42213ba6ef98992f9a6)) + + +### Features + +* **wallet-connect:** update safe-apps-sdk to enable synchronous off-chain signatures ([#657](https://github.com/safe-global/safe-react-apps/issues/657)) ([6b1562f](https://github.com/safe-global/safe-react-apps/commit/6b1562f59eeaff662c212ee0b71f7df602cf0185)) + + + # [1.14.0](https://github.com/safe-global/safe-react-apps/compare/tx-builder-1.13.3...tx-builder-1.14.0) (2023-02-27) diff --git a/apps/tx-builder/config-overrides.js b/apps/tx-builder/config-overrides.js index f4ec1f4f0..a605fe089 100644 --- a/apps/tx-builder/config-overrides.js +++ b/apps/tx-builder/config-overrides.js @@ -26,6 +26,8 @@ module.exports = { }), ]) + config.module.rules.push({ test: /\.m?js/, resolve: { fullySpecified: false } }) + // https://github.com/facebook/create-react-app/issues/11924 config.ignoreWarnings = [/to parse source map/i] diff --git a/apps/tx-builder/package.json b/apps/tx-builder/package.json index 5d3bc37ea..fa41acfc9 100644 --- a/apps/tx-builder/package.json +++ b/apps/tx-builder/package.json @@ -1,16 +1,17 @@ { "name": "tx-builder", - "version": "1.14.0", + "version": "1.18.3", "private": true, "homepage": "/tx-builder", "dependencies": { - "@gnosis.pm/safe-deployments": "^1.19.0", "@gnosis.pm/safe-react-components": "1.1.5", "@material-ui/core": "^4.12.4", "@material-ui/icons": "^4.11.3", "@material-ui/lab": "^4.0.0-alpha.60", - "@safe-global/safe-apps-provider": "^0.16.0", - "axios": "^0.27.2", + "@safe-global/safe-apps-provider": "^0.18.0", + "@safe-global/safe-deployments": "^1.37.45", + "@safe-global/safe-gateway-typescript-sdk": "^3.19.0", + "axios": "^1.8.2", "evm-proxy-detection": "1.0.0", "localforage": "^1.10.0", "react-beautiful-dnd": "^13.1.1", @@ -25,7 +26,7 @@ "@nomiclabs/hardhat-etherscan": "^3.1.5", "@types/react-beautiful-dnd": "^13.1.2", "ethers": "^5.7.2", - "hardhat": "^2.12.6", + "hardhat": "^2.19.0", "hardhat-deploy": "^0.11.22" }, "scripts": { diff --git a/apps/tx-builder/src/assets/add-new-batch.svg b/apps/tx-builder/src/assets/add-new-batch.svg deleted file mode 100644 index 5303c416d..000000000 --- a/apps/tx-builder/src/assets/add-new-batch.svg +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - diff --git a/apps/tx-builder/src/assets/arrowtotheblock.svg b/apps/tx-builder/src/assets/arrowtotheblock.svg new file mode 100644 index 000000000..54436b814 --- /dev/null +++ b/apps/tx-builder/src/assets/arrowtotheblock.svg @@ -0,0 +1,3 @@ + + + diff --git a/apps/tx-builder/src/assets/empty-library-dark.svg b/apps/tx-builder/src/assets/empty-library-dark.svg new file mode 100644 index 000000000..487d38fd7 --- /dev/null +++ b/apps/tx-builder/src/assets/empty-library-dark.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/apps/tx-builder/src/assets/empty-library-light.svg b/apps/tx-builder/src/assets/empty-library-light.svg new file mode 100644 index 000000000..649f8dc7a --- /dev/null +++ b/apps/tx-builder/src/assets/empty-library-light.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/apps/tx-builder/src/assets/empty-library.svg b/apps/tx-builder/src/assets/empty-library.svg deleted file mode 100644 index 7e6198d63..000000000 --- a/apps/tx-builder/src/assets/empty-library.svg +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - - - diff --git a/apps/safe-claiming-app/public/fonts/dm-sans-v11-latin-ext-700.woff2 b/apps/tx-builder/src/assets/fonts/DMSans700.woff2 similarity index 100% rename from apps/safe-claiming-app/public/fonts/dm-sans-v11-latin-ext-700.woff2 rename to apps/tx-builder/src/assets/fonts/DMSans700.woff2 diff --git a/apps/safe-claiming-app/public/fonts/dm-sans-v11-latin-ext-regular.woff2 b/apps/tx-builder/src/assets/fonts/DMSansRegular.woff2 similarity index 100% rename from apps/safe-claiming-app/public/fonts/dm-sans-v11-latin-ext-regular.woff2 rename to apps/tx-builder/src/assets/fonts/DMSansRegular.woff2 diff --git a/apps/tx-builder/src/assets/new-batch-dark.svg b/apps/tx-builder/src/assets/new-batch-dark.svg new file mode 100644 index 000000000..f80dc18a4 --- /dev/null +++ b/apps/tx-builder/src/assets/new-batch-dark.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/apps/tx-builder/src/assets/new-batch-light.svg b/apps/tx-builder/src/assets/new-batch-light.svg new file mode 100644 index 000000000..82b946d2d --- /dev/null +++ b/apps/tx-builder/src/assets/new-batch-light.svg @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/apps/tx-builder/src/components/Accordion/index.tsx b/apps/tx-builder/src/components/Accordion/index.tsx new file mode 100644 index 000000000..36fc14311 --- /dev/null +++ b/apps/tx-builder/src/components/Accordion/index.tsx @@ -0,0 +1,87 @@ +import { ReactElement } from 'react' +import AccordionMUI, { AccordionProps as AccordionMUIProps } from '@material-ui/core/Accordion' +import AccordionSummaryMUI, { + AccordionSummaryProps as AccordionSummaryMUIProps, +} from '@material-ui/core/AccordionSummary' +import styled from 'styled-components' +import FixedIcon from '../FixedIcon' + +type AccordionProps = AccordionMUIProps & { + compact?: boolean +} + +type StyledAccordionProps = AccordionMUIProps & { + $compact?: AccordionProps['compact'] +} + +const StyledAccordion = styled(AccordionMUI)` + &.MuiAccordion-root { + border-radius: ${({ $compact }) => ($compact ? '8px' : '0')}; + border: ${({ $compact, theme }) => ($compact ? '2px solid ' + theme.palette.divider : 'none')}; + border-bottom: 2px solid ${({ theme }) => theme.palette.divider}; + margin-bottom: ${({ $compact }) => ($compact ? '16px' : '0')}; + overflow: hidden; + + &:before { + height: 0; + } + + &:first-child { + border-top: 2px solid ${({ theme }) => theme.palette.divider}; + } + + &.Mui-expanded { + margin: ${({ $compact }) => ($compact ? '0 0 16px 0' : '0')}; + } + + .MuiAccordionDetails-root { + padding: 16px; + } + } +` + +const StyledAccordionSummary = styled(AccordionSummaryMUI)` + &.MuiAccordionSummary-root { + &.Mui-expanded { + min-height: 48px; + border-bottom: 2px solid ${({ theme }) => theme.palette.divider}; + background-color: ${({ theme }) => theme.palette.background.default}; + } + + &:hover { + background-color: ${({ theme }) => theme.palette.background.default}; + } + + .MuiAccordionSummary-content { + &.Mui-expanded { + margin: 0; + } + } + .MuiIconButton-root { + font-size: 0; + padding: 16px; + } + } +` + +export const Accordion = ({ compact, children, ...props }: AccordionProps): ReactElement => { + return ( + + {children} + + ) +} + +export const AccordionSummary = ({ + children, + ...props +}: AccordionSummaryMUIProps): ReactElement => { + return ( + } {...props}> + {children} + + ) +} + +export { default as AccordionActions } from '@material-ui/core/AccordionActions' +export { default as AccordionDetails } from '@material-ui/core/AccordionDetails' diff --git a/apps/tx-builder/src/components/Button.tsx b/apps/tx-builder/src/components/Button.tsx new file mode 100644 index 000000000..3f1edba72 --- /dev/null +++ b/apps/tx-builder/src/components/Button.tsx @@ -0,0 +1,208 @@ +import React, { ReactElement, ReactNode, HTMLAttributes } from 'react' +import ButtonMUI, { ButtonProps as ButtonMUIProps } from '@material-ui/core/Button' +import { alpha } from '@material-ui/core/styles' + +import styled, { css, DefaultTheme, FlattenInterpolation, ThemeProps } from 'styled-components' +import { Icon, IconProps } from './Icon' + +type Colors = 'primary' | 'secondary' | 'error' +type Variations = 'bordered' | 'contained' | 'outlined' + +type CustomButtonMuiProps = Omit & { + to?: string + component?: ReactNode +} +type LocalProps = { + children?: ReactNode + color?: Colors + variant?: Variations + iconType?: IconProps['type'] + iconSize?: IconProps['size'] +} + +type Props = LocalProps & CustomButtonMuiProps & HTMLAttributes + +const StyledIcon = styled(Icon)` + margin-right: 5px; +` + +const customStyles: { + [key in Colors]: { + [key in Variations]: FlattenInterpolation> + } +} = { + primary: { + contained: css` + color: ${({ theme }) => theme.palette.background.main}; + background-color: ${({ theme }) => theme.palette.primary.main}; + box-shadow: 1px 2px 10px ${alpha('#28363D', 0.18)}; + + &:hover { + color: ${({ theme }) => theme.palette.background.main}; + background-color: ${({ theme }) => theme.palette.primary.dark}; + } + `, + outlined: css` + color: ${({ theme }) => theme.palette.primary.main}; + background-color: transparent; + path.icon-color { + fill: ${({ theme }) => theme.palette.primary.main}; + } + + &.Mui-disabled { + color: ${({ theme }) => theme.palette.primary.main}; + } + + &:hover { + color: ${({ theme }) => theme.palette.primary.dark}; + } + `, + bordered: css` + color: ${({ theme }) => theme.palette.primary.main}; + background-color: transparent; + border: 2px solid ${({ theme }) => theme.palette.primary.main}; + path.icon-color { + fill: ${({ theme }) => theme.palette.primary.main}; + } + + &.Mui-disabled { + color: ${({ theme }) => theme.palette.primary.main}; + } + + &:hover { + background: ${({ theme }) => theme.palette.background.light}; + } + `, + }, + secondary: { + contained: css` + color: ${({ theme }) => theme.palette.primary}; + background-color: ${({ theme }) => theme.palette.secondary.main}; + box-shadow: 1px 2px 10px ${alpha('#28363D', 0.18)}; + + path.icon-color { + color: ${({ theme }) => theme.palette.common.primary}; + } + + &:hover { + path.icon-color { + color: ${({ theme }) => theme.palette.common.primary}; + } + + background-color: ${({ theme }) => theme.palette.secondary.dark}; + } + `, + outlined: css` + color: ${({ theme }) => theme.palette.secondary.main}; + background-color: transparent; + path.icon-color { + fill: ${({ theme }) => theme.palette.secondary.main}; + } + + &.Mui-disabled { + color: ${({ theme }) => theme.palette.secondary.main}; + } + `, + bordered: css` + color: ${({ theme }) => theme.palette.secondary.main}; + background-color: transparent; + border: 2px solid ${({ theme }) => theme.palette.secondary.main}; + path.icon-color { + fill: ${({ theme }) => theme.palette.secondary.main}; + } + + &.Mui-disabled { + color: ${({ theme }) => theme.palette.secondary.main}; + } + `, + }, + error: { + contained: css` + color: ${({ theme }) => theme.palette.error.main}; + background-color: ${({ theme }) => theme.palette.error.background}; + + &:hover { + background-color: ${({ theme }) => theme.palette.error.light}; + color: ${({ theme }) => theme.palette.error.dark}; + } + `, + outlined: css` + color: ${({ theme }) => theme.palette.error.main}; + background-color: transparent; + path.icon-color { + fill: ${({ theme }) => theme.palette.error.main}; + } + + &.Mui-disabled { + color: ${({ theme }) => theme.palette.error.main}; + } + `, + bordered: css` + color: ${({ theme }) => theme.palette.error.main}; + background-color: transparent; + border: 2px solid ${({ theme }) => theme.palette.error.main}; + path.icon-color { + fill: ${({ theme }) => theme.palette.error.main}; + } + + &.Mui-disabled { + color: ${({ theme }) => theme.palette.error.main}; + } + `, + }, +} + +const StyledButton = styled(ButtonMUI)<{ $localProps: LocalProps }>` + && { + font-weight: 700; + padding: 8px 1.4rem; + min-width: 120px; + + &.MuiButton-root { + text-transform: none; + border-radius: 8px; + letter-spacing: 0; + } + + &.Mui-disabled { + color: ${({ theme }) => theme.palette.background.main}; + } + + path.icon-color { + fill: ${({ theme }) => theme.palette.background.main}; + } + + &:disabled { + opacity: 0.5; + } + + ${({ $localProps }) => { + if ($localProps.color !== undefined && $localProps.variant !== undefined) { + return customStyles[$localProps.color][$localProps.variant] + } + }} + } +` + +const Button = ({ + children, + color = 'primary', + variant = 'contained', + iconType, + iconSize, + // We need destructuring all LocalProps, remaining props are for CustomButtonMuiProps + ...buttonMuiProps +}: Props): ReactElement => { + return ( + + {iconType && iconSize && } + {children} + + ) +} + +export default Button diff --git a/apps/tx-builder/src/components/Card/index.tsx b/apps/tx-builder/src/components/Card/index.tsx new file mode 100644 index 000000000..83f161ce1 --- /dev/null +++ b/apps/tx-builder/src/components/Card/index.tsx @@ -0,0 +1,36 @@ +import React from 'react' +import styled from 'styled-components' +import { alpha } from '@material-ui/core/styles' + +const StyledCard = styled.div` + box-shadow: 1px 2px 10px 0 ${alpha('#28363D', 0.18)}; + border-radius: 8px; + padding: 24px; + background-color: ${({ theme }) => theme.palette.common.white}; + position: relative; +` + +const Disabled = styled.div` + opacity: 0.5; + position: absolute; + height: 100%; + width: 100%; + background-color: ${({ theme }) => theme.palette.common.white}; + z-index: 1; + top: 0; + left: 0; +` + +type Props = { + className?: string + disabled?: boolean +} & React.HTMLAttributes + +const Card: React.FC = ({ className, children, disabled, ...rest }): React.ReactElement => ( + + {disabled && } + {children} + +) + +export default Card diff --git a/apps/tx-builder/src/components/CreateNewBatchCard.tsx b/apps/tx-builder/src/components/CreateNewBatchCard.tsx index 3ffe0e30e..e1ae18f63 100644 --- a/apps/tx-builder/src/components/CreateNewBatchCard.tsx +++ b/apps/tx-builder/src/components/CreateNewBatchCard.tsx @@ -1,13 +1,18 @@ -import { useRef } from 'react' -import { ButtonLink, Icon, Text } from '@gnosis.pm/safe-react-components' +import { useContext, useRef } from 'react' import { alpha } from '@material-ui/core' import Hidden from '@material-ui/core/Hidden' import styled from 'styled-components' import { useTheme } from '@material-ui/core/styles' -import { ReactComponent as CreateNewBatchSVG } from '../assets/add-new-batch.svg' +import { ReactComponent as CreateNewBatchLightSvg } from '../assets/new-batch-light.svg' +import { ReactComponent as CreateNewBatchDarkSvg } from '../assets/new-batch-dark.svg' +import { ReactComponent as ArrowBlock } from '../assets/arrowtotheblock.svg' import useDropZone from '../hooks/useDropZone' import { useMediaQuery } from '@material-ui/core' +import { Icon } from './Icon' +import Text from './Text' +import ButtonLink from './buttons/ButtonLink' +import { EModes, ThemeModeContext } from '../theme/SafeThemeProvider' type CreateNewBatchCardProps = { onFileSelected: (file: File | null) => void @@ -15,6 +20,7 @@ type CreateNewBatchCardProps = { const CreateNewBatchCard = ({ onFileSelected }: CreateNewBatchCardProps) => { const theme = useTheme() + const mode = useContext(ThemeModeContext) const isSmallScreen = useMediaQuery(theme.breakpoints.down('sm')) const fileRef = useRef(null) @@ -38,36 +44,42 @@ const CreateNewBatchCard = ({ onFileSelected }: CreateNewBatchCardProps) => { return ( - + {mode === EModes.DARK ? : } + - - {isAcceptError ? ( - - The uploaded file is not a valid JSON file - - ) : ( - <> - - Drag and drop a JSON file or - - choose a file - - - )} - - + + + Start creating a new batch + or + + {isAcceptError ? ( + + The uploaded file is not a valid JSON file + + ) : ( + <> + + Drag and drop a JSON file or + + choose a file + + + )} + + + ) } @@ -75,26 +87,48 @@ const CreateNewBatchCard = ({ onFileSelected }: CreateNewBatchCardProps) => { export default CreateNewBatchCard const Wrapper = styled.div<{ isSmallScreen: boolean }>` + text-align: center; + position: relative; margin-top: ${({ isSmallScreen }) => (isSmallScreen ? '0' : '64px')}; ` +const StyledArrowBlock = styled(ArrowBlock)` + position: absolute; + left: -2px; + top: 7rem; +` + +const StyledCreateBatchContent = styled.div` + margin-top: 1rem; + display: flex; + flex-direction: column; + gap: 1rem; +` + const StyledDragAndDropFileContainer = styled.div<{ dragOver: Boolean fullWidth: boolean error: Boolean }>` box-sizing: border-box; - max-width: ${({ fullWidth }) => (fullWidth ? '100%' : '420px')}; - border: 2px dashed ${({ theme, error }) => (error ? theme.colors.error : '#008c73')}; + max-width: ${({ fullWidth }) => (fullWidth ? '100%' : '430px')}; + width: 100%; + border: 2px dashed + ${({ theme, error }) => (error ? theme.palette.error.main : theme.palette.secondary.dark)}; border-radius: 8px; - background-color: ${({ theme, error }) => (error ? alpha(theme.colors.error, 0.7) : '#eaf7f4')}; + background-color: ${({ theme, error }) => + error ? alpha(theme.palette.error.main, 0.7) : theme.palette.secondary.background}; padding: 24px; - margin: 24px auto 0 auto; + margin: 6px auto; display: flex; justify-content: center; align-items: center; + svg { + margin-right: 4px; + } + ${({ dragOver, error, theme }) => { if (dragOver) { return ` @@ -104,22 +138,31 @@ const StyledDragAndDropFileContainer = styled.div<{ } return ` - border-color: ${error ? theme.colors.error : '#008c73'}; - background-color: ${error ? alpha(theme.colors.error, 0.7) : '#eaf7f4'}; + border-color: ${error ? theme.palette.error.main : theme.palette.secondary.dark}; + background-color: ${ + error ? alpha(theme.palette.error.main, 0.7) : theme.palette.secondary.background + }; ` }} ` const StyledText = styled(Text)<{ error?: Boolean }>` - margin-left: 4px; - color: ${({ error }) => (error ? '#FFF' : '#566976')}; + && { + color: ${({ error, theme }) => + error ? theme.palette.common.white : theme.palette.text.secondary}; + } ` const StyledButtonLink = styled(ButtonLink)` + margin-left: 0.3rem; padding: 0; - text-decoration: none; && > p { - font-size: 16px; + color: ${({ theme }) => theme.palette.upload.primary}; + text-decoration: underline; + + &:hover { + color: ${({ theme }) => theme.palette.backdrop.main}; + } } ` diff --git a/apps/tx-builder/src/components/Divider.tsx b/apps/tx-builder/src/components/Divider.tsx new file mode 100644 index 000000000..e423550a1 --- /dev/null +++ b/apps/tx-builder/src/components/Divider.tsx @@ -0,0 +1,29 @@ +import React from 'react' +import styled from 'styled-components' + +type Props = { + className?: string + orientation?: 'vertical' | 'horizontal' +} + +const HorizontalDivider = styled.div` + margin: 16px -1.6rem; + border-top: solid 1px ${({ theme }) => theme.palette.border.light}; + width: calc(100% + 3.2rem); +` + +const VerticalDivider = styled.div` + border-right: 1px solid ${({ theme }) => theme.legacy.colors.separator}; + margin: 0 5px; + height: 100%; +` + +const Divider = ({ className, orientation }: Props): React.ReactElement => { + return orientation === 'vertical' ? ( + + ) : ( + + ) +} + +export default Divider diff --git a/apps/tx-builder/src/components/Dot/index.tsx b/apps/tx-builder/src/components/Dot/index.tsx new file mode 100644 index 000000000..df53a5643 --- /dev/null +++ b/apps/tx-builder/src/components/Dot/index.tsx @@ -0,0 +1,24 @@ +import React from 'react' +import styled from 'styled-components' +import { type Theme } from '@material-ui/core/styles' + +type Props = { + className?: string + color: keyof Theme['palette'] +} + +const StyledDot = styled.div` + display: flex; + align-items: center; + justify-content: center; + border-radius: 50%; + height: 36px; + width: 36px; + background-color: ${({ theme, color }) => theme.palette[color].main}; +` + +const Dot: React.FC = ({ children, ...rest }): React.ReactElement => ( + {children} +) + +export default Dot diff --git a/apps/tx-builder/src/components/ETHHashInfo.tsx b/apps/tx-builder/src/components/ETHHashInfo.tsx new file mode 100644 index 000000000..e03611de0 --- /dev/null +++ b/apps/tx-builder/src/components/ETHHashInfo.tsx @@ -0,0 +1,149 @@ +import React, { useState } from 'react' +import styled from 'styled-components' + +import { BreakpointDefaults } from '@material-ui/core/styles/createBreakpoints' +import { textShortener } from '../utils/strings' +import { EllipsisMenuItem } from '@gnosis.pm/safe-react-components' +import Text from './Text' +import { Theme } from '@material-ui/core' +import ExplorerButton from './buttons/ExplorerButton' +import Identicon, { identiconSizes } from './buttons/Identicon' +import CopyToClipboardBtn from './buttons/CopyToClipboardBtn' +import EllipsisMenu from './EllipsisMenu' + +export type ExplorerInfo = () => { url: string; alt: string } + +const StyledContainer = styled.div` + display: flex; + align-items: center; +` + +const AvatarContainer = styled.div` + display: flex; + margin-right: 8px; +` + +const InfoContainer = styled.div` + display: flex; + align-items: flex-start; + justify-content: center; + flex-direction: column; +` + +const AddressContainer = styled.div` + display: flex; + align-items: center; + gap: 4px; +` + +const StyledImg = styled.img<{ size: keyof typeof identiconSizes }>` + height: ${({ size }) => identiconSizes[size]}; + width: ${({ size }) => identiconSizes[size]}; +` + +type Props = { + className?: string + hash: string + showHash?: boolean + shortenHash?: number + name?: string + strongName?: boolean + textColor?: keyof Theme['palette'] + textSize?: keyof BreakpointDefaults + showAvatar?: boolean + customAvatar?: string + customAvatarFallback?: string + avatarSize?: keyof BreakpointDefaults + showCopyBtn?: boolean + menuItems?: EllipsisMenuItem[] + explorerUrl?: ExplorerInfo +} + +type ShortNameProps = + | { + shouldShowShortName: boolean + shouldCopyShortName?: boolean + shortName: string + } + | { + shouldShowShortName?: boolean + shouldCopyShortName: boolean + shortName: string + } + | { + shouldShowShortName?: never + shouldCopyShortName?: never + shortName?: string + } + +type EthHashInfoProps = Props & ShortNameProps + +const EthHashInfo = ({ + hash, + showHash = true, + name, + className, + shortenHash, + showAvatar, + customAvatar, + customAvatarFallback, + avatarSize = 'md', + showCopyBtn, + menuItems, + explorerUrl, + shortName, + shouldShowShortName, + shouldCopyShortName, +}: EthHashInfoProps): React.ReactElement => { + const [fallbackToIdenticon, setFallbackToIdenticon] = useState(false) + const [fallbackSrc, setFallabckSrc] = useState(undefined) + + const setAppImageFallback = (): void => { + if (customAvatarFallback && !fallbackToIdenticon) { + setFallabckSrc(customAvatarFallback) + } else { + setFallbackToIdenticon(true) + } + } + + return ( + + {showAvatar && ( + + {!fallbackToIdenticon && customAvatar ? ( + + ) : ( + + )} + + )} + + + {name && {name}} + + {showHash && ( + + {shouldShowShortName && ( + + {shortName}: + + )} + {shortenHash ? textShortener(hash, shortenHash + 2, shortenHash) : hash} + + )} + {showCopyBtn && ( + + )} + {explorerUrl && } + {menuItems && } + + + + ) +} + +export default EthHashInfo diff --git a/apps/tx-builder/src/components/EllipsisMenu/index.tsx b/apps/tx-builder/src/components/EllipsisMenu/index.tsx new file mode 100644 index 000000000..ff563b512 --- /dev/null +++ b/apps/tx-builder/src/components/EllipsisMenu/index.tsx @@ -0,0 +1,102 @@ +import { ClickAwayListener } from '@material-ui/core' +import Menu from '@material-ui/core/Menu' +import MenuItem from '@material-ui/core/MenuItem' +import React from 'react' +import styled from 'styled-components' +import FixedIcon from '../FixedIcon' + +const StyledMenu = styled(Menu)` + && { + .MuiMenu-paper { + box-shadow: 0 0 4px rgba(0, 0, 0, 0.1); + } + + .MuiMenu-list { + div:not(:first-child) { + border-top: 1px solid ${({ theme }) => theme.palette.divider}; + } + } + } +` + +const MenuWrapper = styled.div` + display: flex; +` + +const MenuItemWrapper = styled.div` + :focus { + outline-color: ${({ theme }) => theme.palette.divider}; + } +` + +const IconWrapper = styled.button` + background: none; + border: none; + cursor: pointer; + margin: 0; + border-radius: 50%; + transition: background-color 0.2s ease-in-out; + outline-color: transparent; + height: 24px; + width: 24px; + + span { + display: flex; + } + + :hover { + background-color: ${({ theme }) => theme.palette.divider}; + } +` + +export type EllipsisMenuItem = { + label: string + disabled?: boolean + onClick: () => void +} + +type Props = { + menuItems: EllipsisMenuItem[] +} + +const EllipsisMenu = ({ menuItems }: Props): React.ReactElement => { + const [anchorEl, setAnchorEl] = React.useState(null) + + const handleClick = (event: React.MouseEvent): void => + setAnchorEl(event.currentTarget) + + const closeMenuHandler = () => { + setAnchorEl(null) + } + + const onMenuItemClick = (item: EllipsisMenuItem) => { + item.onClick() + closeMenuHandler() + } + + return ( + + + + + + + {menuItems.map(item => ( + + onMenuItemClick(item)}> + {item.label} + + + ))} + + + + ) +} + +export default EllipsisMenu diff --git a/apps/tx-builder/src/components/FixedIcon/images/arrowReceived.tsx b/apps/tx-builder/src/components/FixedIcon/images/arrowReceived.tsx new file mode 100644 index 000000000..20ab6167d --- /dev/null +++ b/apps/tx-builder/src/components/FixedIcon/images/arrowReceived.tsx @@ -0,0 +1,12 @@ +const icon = ( + + + +) + +export default icon diff --git a/apps/tx-builder/src/components/FixedIcon/images/arrowReceivedWhite.tsx b/apps/tx-builder/src/components/FixedIcon/images/arrowReceivedWhite.tsx new file mode 100644 index 000000000..07d980afd --- /dev/null +++ b/apps/tx-builder/src/components/FixedIcon/images/arrowReceivedWhite.tsx @@ -0,0 +1,14 @@ +import React from 'react' + +const icon = ( + + + +) + +export default icon diff --git a/apps/tx-builder/src/components/FixedIcon/images/arrowSent.tsx b/apps/tx-builder/src/components/FixedIcon/images/arrowSent.tsx new file mode 100644 index 000000000..9fa0ddf79 --- /dev/null +++ b/apps/tx-builder/src/components/FixedIcon/images/arrowSent.tsx @@ -0,0 +1,14 @@ +import React from 'react' + +const icon = ( + + + +) + +export default icon diff --git a/apps/tx-builder/src/components/FixedIcon/images/arrowSentWhite.tsx b/apps/tx-builder/src/components/FixedIcon/images/arrowSentWhite.tsx new file mode 100644 index 000000000..3818a64a5 --- /dev/null +++ b/apps/tx-builder/src/components/FixedIcon/images/arrowSentWhite.tsx @@ -0,0 +1,14 @@ +import React from 'react' + +const icon = ( + + + +) + +export default icon diff --git a/apps/tx-builder/src/components/FixedIcon/images/arrowSort.tsx b/apps/tx-builder/src/components/FixedIcon/images/arrowSort.tsx new file mode 100644 index 000000000..598716081 --- /dev/null +++ b/apps/tx-builder/src/components/FixedIcon/images/arrowSort.tsx @@ -0,0 +1,14 @@ +import React from 'react' + +const icon = ( + + + +) + +export default icon diff --git a/apps/tx-builder/src/components/FixedIcon/images/bullit.tsx b/apps/tx-builder/src/components/FixedIcon/images/bullit.tsx new file mode 100644 index 000000000..3e9569207 --- /dev/null +++ b/apps/tx-builder/src/components/FixedIcon/images/bullit.tsx @@ -0,0 +1,14 @@ +import React from 'react' + +const icon = ( + + + +) + +export default icon diff --git a/apps/tx-builder/src/components/FixedIcon/images/chevronDown.tsx b/apps/tx-builder/src/components/FixedIcon/images/chevronDown.tsx new file mode 100644 index 000000000..9e9aaa309 --- /dev/null +++ b/apps/tx-builder/src/components/FixedIcon/images/chevronDown.tsx @@ -0,0 +1,14 @@ +import React from 'react' + +const icon = ( + + + +) + +export default icon diff --git a/apps/tx-builder/src/components/FixedIcon/images/chevronLeft.tsx b/apps/tx-builder/src/components/FixedIcon/images/chevronLeft.tsx new file mode 100644 index 000000000..fcd0c42e7 --- /dev/null +++ b/apps/tx-builder/src/components/FixedIcon/images/chevronLeft.tsx @@ -0,0 +1,14 @@ +import React from 'react' + +const icon = ( + + + +) + +export default icon diff --git a/apps/tx-builder/src/components/FixedIcon/images/chevronRight.tsx b/apps/tx-builder/src/components/FixedIcon/images/chevronRight.tsx new file mode 100644 index 000000000..42209a6dd --- /dev/null +++ b/apps/tx-builder/src/components/FixedIcon/images/chevronRight.tsx @@ -0,0 +1,14 @@ +import React from 'react' + +const icon = ( + + + +) + +export default icon diff --git a/apps/tx-builder/src/components/FixedIcon/images/chevronUp.tsx b/apps/tx-builder/src/components/FixedIcon/images/chevronUp.tsx new file mode 100644 index 000000000..5d6bdc440 --- /dev/null +++ b/apps/tx-builder/src/components/FixedIcon/images/chevronUp.tsx @@ -0,0 +1,14 @@ +import React from 'react' + +const icon = ( + + + +) + +export default icon diff --git a/apps/tx-builder/src/components/FixedIcon/images/connectedRinkeby.tsx b/apps/tx-builder/src/components/FixedIcon/images/connectedRinkeby.tsx new file mode 100644 index 000000000..98a397a7a --- /dev/null +++ b/apps/tx-builder/src/components/FixedIcon/images/connectedRinkeby.tsx @@ -0,0 +1,13 @@ +import React from 'react' + +const icon = ( + + + +) + +export default icon diff --git a/apps/tx-builder/src/components/FixedIcon/images/connectedWallet.tsx b/apps/tx-builder/src/components/FixedIcon/images/connectedWallet.tsx new file mode 100644 index 000000000..53bd24a1b --- /dev/null +++ b/apps/tx-builder/src/components/FixedIcon/images/connectedWallet.tsx @@ -0,0 +1,13 @@ +import React from 'react' + +const icon = ( + + + +) + +export default icon diff --git a/apps/tx-builder/src/components/FixedIcon/images/creatingInProgress.tsx b/apps/tx-builder/src/components/FixedIcon/images/creatingInProgress.tsx new file mode 100644 index 000000000..542b2c4ed --- /dev/null +++ b/apps/tx-builder/src/components/FixedIcon/images/creatingInProgress.tsx @@ -0,0 +1,17 @@ +import React from 'react' + +const icon = ( + + + + + + +) + +export default icon diff --git a/apps/tx-builder/src/components/FixedIcon/images/dropdownArrowSmall.tsx b/apps/tx-builder/src/components/FixedIcon/images/dropdownArrowSmall.tsx new file mode 100644 index 000000000..0bc2c5d75 --- /dev/null +++ b/apps/tx-builder/src/components/FixedIcon/images/dropdownArrowSmall.tsx @@ -0,0 +1,13 @@ +import React from 'react' + +const icon = ( + + + +) + +export default icon diff --git a/apps/tx-builder/src/components/FixedIcon/images/networkError.tsx b/apps/tx-builder/src/components/FixedIcon/images/networkError.tsx new file mode 100644 index 000000000..daff9c9e3 --- /dev/null +++ b/apps/tx-builder/src/components/FixedIcon/images/networkError.tsx @@ -0,0 +1,26 @@ +import React from 'react' + +const icon = ( + + + + + + + + +) + +export default icon diff --git a/apps/tx-builder/src/components/FixedIcon/images/notConnected.tsx b/apps/tx-builder/src/components/FixedIcon/images/notConnected.tsx new file mode 100644 index 000000000..1b2b19246 --- /dev/null +++ b/apps/tx-builder/src/components/FixedIcon/images/notConnected.tsx @@ -0,0 +1,18 @@ +import React from 'react' + +const icon = ( + + + + + + + + + +) + +export default icon diff --git a/apps/tx-builder/src/components/FixedIcon/images/notOwner.tsx b/apps/tx-builder/src/components/FixedIcon/images/notOwner.tsx new file mode 100644 index 000000000..e2ea0945d --- /dev/null +++ b/apps/tx-builder/src/components/FixedIcon/images/notOwner.tsx @@ -0,0 +1,34 @@ +import React from 'react' + +const icon = ( + + + + + + + + + + + +) + +export default icon diff --git a/apps/tx-builder/src/components/FixedIcon/images/options.tsx b/apps/tx-builder/src/components/FixedIcon/images/options.tsx new file mode 100644 index 000000000..f8689a2ff --- /dev/null +++ b/apps/tx-builder/src/components/FixedIcon/images/options.tsx @@ -0,0 +1,13 @@ +import React from 'react' + +const icon = ( + + + + + + + +) + +export default icon diff --git a/apps/tx-builder/src/components/FixedIcon/images/plus.tsx b/apps/tx-builder/src/components/FixedIcon/images/plus.tsx new file mode 100644 index 000000000..526b2fb64 --- /dev/null +++ b/apps/tx-builder/src/components/FixedIcon/images/plus.tsx @@ -0,0 +1,20 @@ +const icon = ( + + + + +) + +export default icon diff --git a/apps/tx-builder/src/components/FixedIcon/images/settingsChange.tsx b/apps/tx-builder/src/components/FixedIcon/images/settingsChange.tsx new file mode 100644 index 000000000..bec1264e9 --- /dev/null +++ b/apps/tx-builder/src/components/FixedIcon/images/settingsChange.tsx @@ -0,0 +1,14 @@ +import React from 'react' + +const icon = ( + + + +) + +export default icon diff --git a/apps/tx-builder/src/components/FixedIcon/images/threeDots.tsx b/apps/tx-builder/src/components/FixedIcon/images/threeDots.tsx new file mode 100644 index 000000000..7a8eda5e9 --- /dev/null +++ b/apps/tx-builder/src/components/FixedIcon/images/threeDots.tsx @@ -0,0 +1,13 @@ +import React from 'react' + +const icon = ( + + + + + + + +) + +export default icon diff --git a/apps/tx-builder/src/components/FixedIcon/index.tsx b/apps/tx-builder/src/components/FixedIcon/index.tsx new file mode 100644 index 000000000..65c33bba6 --- /dev/null +++ b/apps/tx-builder/src/components/FixedIcon/index.tsx @@ -0,0 +1,74 @@ +import React from 'react' + +import arrowSort from './images/arrowSort' +import connectedRinkeby from './images/connectedRinkeby' +import connectedWallet from './images/connectedWallet' +import bullit from './images/bullit' +import dropdownArrowSmall from './images/dropdownArrowSmall' +import arrowReceived from './images/arrowReceived' +import arrowReceivedWhite from './images/arrowReceivedWhite' +import arrowSent from './images/arrowSent' +import arrowSentWhite from './images/arrowSentWhite' +import threeDots from './images/threeDots' +import options from './images/options' +import plus from './images/plus' +import chevronRight from './images/chevronRight' +import chevronLeft from './images/chevronLeft' +import chevronUp from './images/chevronUp' +import chevronDown from './images/chevronDown' +import settingsChange from './images/settingsChange' +import creatingInProgress from './images/creatingInProgress' +import notOwner from './images/notOwner' +import notConnected from './images/notConnected' +import networkError from './images/networkError' +import styled from 'styled-components' + +const StyledIcon = styled.span` + .icon-color { + fill: ${({ theme }) => theme.palette.text.primary}; + } + + .icon-stroke { + fill: ${({ theme }) => theme.palette.text.primary}; + } +` +const icons = { + arrowSort, + connectedRinkeby, + connectedWallet, + bullit, + dropdownArrowSmall, + arrowReceived, + arrowReceivedWhite, + arrowSent, + arrowSentWhite, + threeDots, + options, + plus, + chevronRight, + chevronLeft, + chevronUp, + chevronDown, + settingsChange, + creatingInProgress, + notOwner, + notConnected, + networkError, +} + +export type IconType = typeof icons +export type IconTypes = keyof IconType + +type Props = { + type: IconTypes + className?: string +} + +/** + * The `FixedIcon` renders an icon + */ +function FixedIcon({ type, className }: Props): React.ReactElement { + return {icons[type]} +} + +export default FixedIcon diff --git a/apps/tx-builder/src/components/GenericModal.tsx b/apps/tx-builder/src/components/GenericModal.tsx new file mode 100644 index 000000000..365838180 --- /dev/null +++ b/apps/tx-builder/src/components/GenericModal.tsx @@ -0,0 +1,124 @@ +import React from 'react' +import Modal from '@material-ui/core/Modal' +import { makeStyles } from '@material-ui/core/styles' +import { alpha } from '@material-ui/core/styles' +import styled from 'styled-components' +import Media from 'react-media' +import { Typography } from '@material-ui/core' +import { Icon } from './Icon' + +const StyledButton = styled.button` + background: none; + border: none; + padding: 5px; + width: 26px; + height: 26px; + + span { + margin-right: 0; + } + + :focus { + outline: none; + } + + :hover { + background: ${({ theme }) => theme.palette.divider}; + border-radius: 16px; + } +` + +const TitleSection = styled.div` + display: flex; + justify-content: space-between; + padding: 16px 24px; + border-bottom: 2px solid ${({ theme }) => theme.palette.divider}; +` + +const BodySection = styled.div<{ + withoutBodyPadding?: boolean + smallHeight: boolean +}>` + max-height: ${({ smallHeight }) => (smallHeight ? '280px' : '460px')}; + overflow-y: auto; + padding: ${({ withoutBodyPadding }) => (withoutBodyPadding ? '0' : '16px 24px')}; +` + +const FooterSection = styled.div` + border-top: 2px solid ${({ theme }) => theme.palette.divider}; + padding: 16px 24px; +` + +const ModalPaper = styled.div` + background: ${({ theme }) => theme.palette.background.paper}; + color: ${({ theme }) => theme.palette.text.primary}; +` + +export type GenericModalProps = { + title: string | React.ReactNode + body: React.ReactNode + withoutBodyPadding?: boolean + footer?: React.ReactNode + onClose: () => void +} + +const useStyles = makeStyles({ + modal: { + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + overflowY: 'scroll', + background: alpha('#E8E7E6', 0.75), + }, + + paper: { + position: (props: { smallHeight: boolean }) => (props.smallHeight ? 'relative' : 'absolute'), + top: (props: { smallHeight: boolean }) => (props.smallHeight ? 'unset' : '121px'), + minWidth: '500px', + width: (props: { smallHeight: boolean }) => (props.smallHeight ? '500px' : 'inherit'), + borderRadius: '8px', + boxShadow: `0 0 0.75 0 #28363D`, + + '&:focus': { + outline: 'none', + }, + }, +}) + +const GenericModalComponent = ({ + body, + footer, + onClose, + title, + withoutBodyPadding, + smallHeight, +}: GenericModalProps & { smallHeight: boolean }) => { + const classes = useStyles({ smallHeight }) + + return ( + + + + {title} + + + + + + + {body} + + + {footer && {footer}} + + + ) +} + +const GenericModal = (props: GenericModalProps): React.ReactElement => ( + + {matches => } + +) + +export default GenericModal diff --git a/apps/tx-builder/src/components/Header.test.tsx b/apps/tx-builder/src/components/Header.test.tsx index 7c1500bfc..25f547ffc 100644 --- a/apps/tx-builder/src/components/Header.test.tsx +++ b/apps/tx-builder/src/components/Header.test.tsx @@ -3,6 +3,14 @@ import { screen, waitFor } from '@testing-library/react' import { render } from '../test-utils' import Header from './Header' +// Axios is bundled as ESM module which is not directly compatible with Jest +// https://jestjs.io/docs/ecmascript-modules +jest.mock('axios', () => ({ + get: jest.fn(), + post: jest.fn(), + delete: jest.fn(), +})) + describe('
', () => { it('Renders Header component', async () => { render(
) diff --git a/apps/tx-builder/src/components/Header.tsx b/apps/tx-builder/src/components/Header.tsx index 5e105bcb4..711c45461 100644 --- a/apps/tx-builder/src/components/Header.tsx +++ b/apps/tx-builder/src/components/Header.tsx @@ -1,4 +1,3 @@ -import { FixedIcon, Icon, Text, Title, Tooltip } from '@gnosis.pm/safe-react-components' import { Link, useLocation, useNavigate } from 'react-router-dom' import styled from 'styled-components' @@ -12,9 +11,13 @@ import { import { useTransactionLibrary } from '../store' import ChecksumWarning from './ChecksumWarning' import ErrorAlert from './ErrorAlert' +import { Tooltip } from './Tooltip' +import { Icon } from './Icon' +import FixedIcon from './FixedIcon' +import { Typography } from '@material-ui/core' +import Text from './Text' -const HELP_ARTICLE_LINK = - 'https://help.gnosis-safe.io/en/articles/4680071-create-a-batched-transaction-with-the-transaction-builder-safe-app' +const HELP_ARTICLE_LINK = 'https://help.safe.global/en/articles/234052-transaction-builder' const goBackLabel: Record = { [CREATE_BATCH_PATH]: 'Back to Transaction Creation', @@ -52,31 +55,25 @@ const Header = () => { {showTitle ? ( <> {/* Transaction Builder Title */} - Transaction Builder - - + Transaction Builder + + - + ) : ( {/* Go Back link */} - {goBackLabel[previousUrl]} + {goBackLabel[previousUrl]} )} {showLinkToLibrary && ( - {`(${batches.length}) Your transaction library`} + {`(${batches.length}) Your transaction library`} @@ -95,29 +92,44 @@ const HeaderWrapper = styled.header` width: 100%; display: flex; align-items: center; - border-bottom: 1px solid #e2e3e3; + border-bottom: 1px solid ${({ theme }) => theme.palette.border.light}; z-index: 10; - background-color: white; + background-color: ${({ theme }) => theme.palette.background.paper}; + color: ${({ theme }) => theme.palette.text.primary}; height: 70px; padding: 0 40px; box-sizing: border-box; ` -const StyledTitle = styled(Title)` - font-size: 20px; - margin: 0 10px 0 0; +const StyledTitle = styled(Typography)` + && { + font-size: 20px; + font-weight: 700; + margin: 0 10px 0 0; + } ` const StyledLink = styled(Link)` display: flex; align-items: center; - color: #000000; + color: ${({ theme }) => theme.palette.common.black}; font-size: 16px; text-decoration: none; + + > span { + padding-top: 3px; + + path { + fill: ${({ theme }) => theme.palette.common.black}; + } + } ` const StyledLeftLinkLabel = styled(Text)` - margin-left: 8px; + && { + margin-left: 8px; + font-weight: 700; + } ` const RigthLinkWrapper = styled.div` @@ -127,5 +139,13 @@ const RigthLinkWrapper = styled.div` ` const StyledRightLinkLabel = styled(Text)` - margin-right: 8px; + && { + font-weight: 700; + margin-right: 8px; + } +` + +const StyledIconLink = styled.a` + display: flex; + align-items: center; ` diff --git a/apps/tx-builder/src/components/Icon/images/alert.tsx b/apps/tx-builder/src/components/Icon/images/alert.tsx new file mode 100644 index 000000000..b1618a408 --- /dev/null +++ b/apps/tx-builder/src/components/Icon/images/alert.tsx @@ -0,0 +1,34 @@ +const Alert = { + sm: ( + + + + + + + + ), + md: ( + + + + + + + + ), +} + +export default Alert diff --git a/apps/tx-builder/src/components/Icon/images/bookmark.tsx b/apps/tx-builder/src/components/Icon/images/bookmark.tsx new file mode 100644 index 000000000..ec17e2171 --- /dev/null +++ b/apps/tx-builder/src/components/Icon/images/bookmark.tsx @@ -0,0 +1,26 @@ +const Bookmark = { + sm: ( + + + + ), + md: ( + + + + ), +} + +export default Bookmark diff --git a/apps/tx-builder/src/components/Icon/images/bookmarkFilled.tsx b/apps/tx-builder/src/components/Icon/images/bookmarkFilled.tsx new file mode 100644 index 000000000..c0128163d --- /dev/null +++ b/apps/tx-builder/src/components/Icon/images/bookmarkFilled.tsx @@ -0,0 +1,30 @@ +const BookMarkFilled = { + sm: ( + + + + + + ), + md: ( + + + + + + ), +} + +export default BookMarkFilled diff --git a/apps/tx-builder/src/components/Icon/images/check.tsx b/apps/tx-builder/src/components/Icon/images/check.tsx new file mode 100644 index 000000000..da0c7925c --- /dev/null +++ b/apps/tx-builder/src/components/Icon/images/check.tsx @@ -0,0 +1,26 @@ +const Check = { + sm: ( + + + + + + + ), + md: ( + + + + + + + ), +} + +export default Check diff --git a/apps/tx-builder/src/components/Icon/images/code.tsx b/apps/tx-builder/src/components/Icon/images/code.tsx new file mode 100644 index 000000000..10b25bbf8 --- /dev/null +++ b/apps/tx-builder/src/components/Icon/images/code.tsx @@ -0,0 +1,27 @@ +const Code = { + sm: ( + + + + + + + ), + md: ( + + + + + + + ), +} + +export default Code diff --git a/apps/tx-builder/src/components/Icon/images/copy.tsx b/apps/tx-builder/src/components/Icon/images/copy.tsx new file mode 100644 index 000000000..652ff813e --- /dev/null +++ b/apps/tx-builder/src/components/Icon/images/copy.tsx @@ -0,0 +1,28 @@ +const Copy = { + sm: ( + + + + + + + ), + md: ( + + + + + + + ), +} + +export default Copy diff --git a/apps/tx-builder/src/components/Icon/images/cross.tsx b/apps/tx-builder/src/components/Icon/images/cross.tsx new file mode 100644 index 000000000..6c9eb6758 --- /dev/null +++ b/apps/tx-builder/src/components/Icon/images/cross.tsx @@ -0,0 +1,28 @@ +import React from 'react' + +const Cross = { + sm: ( + + + + + + + ), + md: ( + + + + + + + ), +} + +export default Cross diff --git a/apps/tx-builder/src/components/Icon/images/delete.tsx b/apps/tx-builder/src/components/Icon/images/delete.tsx new file mode 100644 index 000000000..8a60a4bbc --- /dev/null +++ b/apps/tx-builder/src/components/Icon/images/delete.tsx @@ -0,0 +1,54 @@ +const Delete = { + sm: ( + + + + + + ), + md: ( + + + + + + ), +} + +export default Delete diff --git a/apps/tx-builder/src/components/Icon/images/edit.tsx b/apps/tx-builder/src/components/Icon/images/edit.tsx new file mode 100644 index 000000000..4fb1e7329 --- /dev/null +++ b/apps/tx-builder/src/components/Icon/images/edit.tsx @@ -0,0 +1,26 @@ +const Edit = { + sm: ( + + + + ), + md: ( + + + + ), +} + +export default Edit diff --git a/apps/tx-builder/src/components/Icon/images/externalLink.tsx b/apps/tx-builder/src/components/Icon/images/externalLink.tsx new file mode 100644 index 000000000..0e80e8874 --- /dev/null +++ b/apps/tx-builder/src/components/Icon/images/externalLink.tsx @@ -0,0 +1,36 @@ +const ExternalLink = { + sm: ( + + + + + + + + ), + md: ( + + + + + + + + ), +} + +export default ExternalLink diff --git a/apps/tx-builder/src/components/Icon/images/import.tsx b/apps/tx-builder/src/components/Icon/images/import.tsx new file mode 100644 index 000000000..051dfdd5a --- /dev/null +++ b/apps/tx-builder/src/components/Icon/images/import.tsx @@ -0,0 +1,40 @@ +const Import = { + sm: ( + + + + + ), + md: ( + + + + + ), +} + +export default Import diff --git a/apps/tx-builder/src/components/Icon/images/info.tsx b/apps/tx-builder/src/components/Icon/images/info.tsx new file mode 100644 index 000000000..3c1485c86 --- /dev/null +++ b/apps/tx-builder/src/components/Icon/images/info.tsx @@ -0,0 +1,40 @@ +const Info = { + sm: ( + + + + + + + + + ), + md: ( + + + + + + + + + ), +} + +export default Info diff --git a/apps/tx-builder/src/components/Icon/images/termsOfUse.tsx b/apps/tx-builder/src/components/Icon/images/termsOfUse.tsx new file mode 100644 index 000000000..6a300b2dd --- /dev/null +++ b/apps/tx-builder/src/components/Icon/images/termsOfUse.tsx @@ -0,0 +1,26 @@ +const TermsOfUse = { + sm: ( + + + + ), + md: ( + + + + ), +} + +export default TermsOfUse diff --git a/apps/tx-builder/src/components/Icon/index.tsx b/apps/tx-builder/src/components/Icon/index.tsx new file mode 100644 index 000000000..0830ccf30 --- /dev/null +++ b/apps/tx-builder/src/components/Icon/index.tsx @@ -0,0 +1,77 @@ +import React from 'react' +import styled from 'styled-components' + +import { type Theme } from '@material-ui/core/styles' +import { Tooltip } from '../Tooltip' + +import alert from './images/alert' +import bookmark from './images/bookmark' +import bookmarkFilled from './images/bookmarkFilled' +import check from './images/check' +import code from './images/code' +import copy from './images/copy' +import cross from './images/cross' +import deleteIcon from './images/delete' +import edit from './images/edit' +import externalLink from './images/externalLink' +import importImg from './images/import' +import info from './images/info' +import termsOfUse from './images/termsOfUse' + +const StyledIcon = styled.span<{ color?: keyof Theme['palette'] }>` + display: inline-flex; + + .icon-color { + fill: ${({ theme, color }) => (color ? theme.palette[color].main : '#B2B5B2')}; + } + + .icon-stroke { + stroke: ${({ theme, color }) => (color ? theme.palette[color].main : '#B2B5B2')}; + } +` + +const icons = { + alert, + bookmark, + bookmarkFilled, + check, + copy, + code, + cross, + delete: deleteIcon, + edit, + externalLink, + importImg, + info, + termsOfUse, +} + +export type IconType = typeof icons +export type IconTypes = keyof IconType + +export type IconProps = { + type: IconTypes + size: 'sm' | 'md' + color?: keyof Theme['palette'] + tooltip?: string + className?: string +} + +/** + * The `Icon` renders an icon, it can be one already defined specified by + * the type Iconprops or custom one using the customUrl. + */ +export const Icon = ({ type, size, color, tooltip, className }: IconProps): React.ReactElement => { + const IconElement = ( + + {icons[type][size]} + + ) + return tooltip === undefined ? ( + IconElement + ) : ( + + {IconElement} + + ) +} diff --git a/apps/tx-builder/src/components/IconText/index.tsx b/apps/tx-builder/src/components/IconText/index.tsx new file mode 100644 index 000000000..f2ec58512 --- /dev/null +++ b/apps/tx-builder/src/components/IconText/index.tsx @@ -0,0 +1,73 @@ +import React from 'react' +import styled from 'styled-components' +import { type Theme } from '@material-ui/core/styles' + +import { Icon, IconProps, IconType } from '../Icon' +import Text from '../Text' + +const iconTextMargins = { + xxs: '4px', + xs: '6px', + sm: '8px', + md: '12px', + lg: '16px', + xl: '20px', + xxl: '24px', +} + +type IconMargins = keyof typeof iconTextMargins + +type Props = { + iconType: keyof IconType + iconSize: IconProps['size'] + iconColor?: keyof Theme['palette'] + margin?: IconMargins + color?: keyof Theme['palette'] + text: string + className?: string + iconSide?: 'left' | 'right' +} + +const LeftIconText = styled.div<{ margin: IconMargins }>` + display: flex; + align-items: center; + svg { + margin: 0 ${({ margin }) => iconTextMargins[margin]} 0 0; + } +` + +const RightIconText = styled.div<{ margin: IconMargins }>` + display: flex; + align-items: center; + svg { + margin: 0 0 0 ${({ margin }) => iconTextMargins[margin]}; + } +` + +/** + * The `IconText` renders an icon next to a text + */ +const IconText = ({ + iconSize, + margin = 'xs', + iconType, + iconColor, + text, + iconSide = 'left', + color, + className, +}: Props): React.ReactElement => { + return iconSide === 'right' ? ( + + {text} + + + ) : ( + + + {text} + + ) +} + +export default IconText diff --git a/apps/tx-builder/src/components/Link/index.tsx b/apps/tx-builder/src/components/Link/index.tsx new file mode 100644 index 000000000..444fb3df8 --- /dev/null +++ b/apps/tx-builder/src/components/Link/index.tsx @@ -0,0 +1,21 @@ +import React from 'react' +import styled from 'styled-components' +import { type Theme } from '@material-ui/core/styles' + +export interface Props extends React.AnchorHTMLAttributes { + color?: keyof Theme['palette'] | 'white' +} + +const StyledLink = styled.a` + cursor: pointer; + color: ${({ theme, color = 'primary' }) => + color === 'white' ? theme.palette.common.white : theme.palette[color].dark}; + font-family: ${({ theme }) => theme.typography.fontFamily}; + text-decoration: underline; +` + +const Link: React.FC = ({ children, ...rest }): React.ReactElement => { + return {children} +} + +export default Link diff --git a/apps/tx-builder/src/components/Loader/index.tsx b/apps/tx-builder/src/components/Loader/index.tsx new file mode 100644 index 000000000..c0e6d75bf --- /dev/null +++ b/apps/tx-builder/src/components/Loader/index.tsx @@ -0,0 +1,34 @@ +import React from 'react' +import styled from 'styled-components' +import CircularProgress from '@material-ui/core/CircularProgress' +import { type Theme } from '@material-ui/core/styles' + +const loaderSizes = { + xxs: '10px', + xs: '16px', + sm: '30px', + md: '50px', + lg: '70px', +} + +type Props = { + size: keyof typeof loaderSizes + color?: keyof Theme['palette'] + className?: string +} + +const StyledCircularProgress = styled( + ({ size, className }: Props): React.ReactElement => ( + + ), +)` + &.MuiCircularProgress-colorPrimary { + color: ${({ theme, color = 'primary' }) => theme.palette[color].main}; + } +` + +const Loader = ({ className, size, color }: Props): React.ReactElement => ( + +) + +export default Loader diff --git a/apps/tx-builder/src/components/QuickTip.tsx b/apps/tx-builder/src/components/QuickTip.tsx index ac1a46c48..b3382bc79 100644 --- a/apps/tx-builder/src/components/QuickTip.tsx +++ b/apps/tx-builder/src/components/QuickTip.tsx @@ -1,8 +1,7 @@ -import { Icon } from '@gnosis.pm/safe-react-components' import MuiAlert from '@material-ui/lab/Alert' import MuiAlertTitle from '@material-ui/lab/AlertTitle' -import React from 'react' import styled from 'styled-components' +import { Icon } from './Icon' type QuickTipProps = { onClose: () => void @@ -23,11 +22,10 @@ const QuickTip = ({ onClose }: QuickTipProps) => { const StyledAlert = styled(MuiAlert)` && { - font-family: 'Averta'; font-size: 14px; padding: 24px; - background: #eaf7f4; - color: #566976; + background: ${({ theme }) => theme.palette.secondary.background}; + color: ${({ theme }) => theme.palette.text.primary}; border-radius: 8px; .MuiAlert-action { diff --git a/apps/tx-builder/src/components/ShowMoreText.tsx b/apps/tx-builder/src/components/ShowMoreText.tsx index 83e150bbc..b920d99e3 100644 --- a/apps/tx-builder/src/components/ShowMoreText.tsx +++ b/apps/tx-builder/src/components/ShowMoreText.tsx @@ -1,5 +1,5 @@ import { useState, SyntheticEvent } from 'react' -import { Link } from '@gnosis.pm/safe-react-components' +import Link from './Link' type ShowMoreTextProps = { children: string diff --git a/apps/tx-builder/src/components/Switch.tsx b/apps/tx-builder/src/components/Switch.tsx new file mode 100644 index 000000000..dd07fc370 --- /dev/null +++ b/apps/tx-builder/src/components/Switch.tsx @@ -0,0 +1,45 @@ +import React from 'react' +import SwitchMui from '@material-ui/core/Switch' +import styled from 'styled-components' +import { alpha } from '@material-ui/core/styles' + +const StyledSwitch = styled(({ ...rest }) => )` + && { + .MuiSwitch-thumb { + background: ${({ theme, checked }) => (checked ? '#12FF80' : theme.palette.common.white)}; + box-shadow: + 1px 1px 2px rgba(0, 0, 0, 0.2), + 0 0 1px rgba(0, 0, 0, 0.5); + } + + .MuiSwitch-track { + background: ${({ theme }) => theme.palette.common.black}; + } + + .MuiIconButton-label, + .MuiSwitch-colorSecondary.Mui-checked { + color: ${({ checked, theme }) => (checked ? theme.palette.secondary.dark : '#B2B5B2')}; + } + + .MuiSwitch-colorSecondary.Mui-checked:hover { + background-color: ${({ theme }) => alpha(theme.palette.secondary.dark, 0.08)}; + } + + .Mui-checked + .MuiSwitch-track { + background-color: ${({ theme }) => theme.palette.secondary.dark}; + } + } +` + +type Props = { + checked: boolean + onChange: (checked: boolean) => void +} + +const Switch = ({ checked, onChange }: Props): React.ReactElement => { + const onSwitchChange = (_event: any, checked: boolean) => onChange(checked) + + return +} + +export default Switch diff --git a/apps/tx-builder/src/components/Text.tsx b/apps/tx-builder/src/components/Text.tsx new file mode 100644 index 000000000..e3fa2062d --- /dev/null +++ b/apps/tx-builder/src/components/Text.tsx @@ -0,0 +1,66 @@ +import React from 'react' +import Tooltip from '@material-ui/core/Tooltip' +import { withStyles, alpha } from '@material-ui/core/styles' +import { type Theme } from '@material-ui/core/styles' + +import { Typography, TypographyProps } from '@material-ui/core' +import styled from 'styled-components' + +type Props = { + children: React.ReactNode + tooltip?: string + color?: keyof Theme['palette'] | 'white' + className?: string + component?: 'span' | 'p' + strong?: boolean + center?: boolean +} + +const StyledTooltip = withStyles(theme => ({ + tooltip: { + backgroundColor: theme.palette.common.white, + color: theme.palette.text.primary, + boxShadow: `0px 0px 10px ${alpha('#28363D', 0.2)}`, + }, + arrow: { + color: theme.palette.common.white, + boxShadow: 'transparent', + }, +}))(Tooltip) + +const StyledTypography = styled(Typography)<{ $color?: keyof Theme['palette'] | 'white' } & Props>` + color: ${({ $color, theme }) => + $color + ? $color === 'white' + ? theme.palette.common.white + : theme.palette[$color].main + : theme.palette.text.primary}; + + ${({ center }) => center && 'text-align: center;'} + + ${({ strong }) => strong && `font-weight: bold;`} +` + +const Text = ({ + children, + component = 'p', + tooltip, + color, + ...rest +}: Props & Omit): React.ReactElement => { + const TextElement = ( + + {children} + + ) + + return tooltip === undefined ? ( + TextElement + ) : ( + + {TextElement} + + ) +} + +export default Text diff --git a/apps/tx-builder/src/components/Title.tsx b/apps/tx-builder/src/components/Title.tsx new file mode 100644 index 000000000..7600e6c20 --- /dev/null +++ b/apps/tx-builder/src/components/Title.tsx @@ -0,0 +1,72 @@ +import { BreakpointDefaults } from '@material-ui/core/styles/createBreakpoints' +import React from 'react' +import styled from 'styled-components' + +type Props = { + children: string | React.ReactNode + size: keyof BreakpointDefaults + withoutMargin?: boolean + strong?: boolean +} + +const StyledH1 = styled.h1<{ withoutMargin?: boolean; strong?: boolean }>` + font-family: ${({ theme }) => theme.legacy.fonts.fontFamily}; + font-size: ${({ theme }) => theme.legacy.title.size.xl.fontSize}; + line-height: ${({ theme }) => theme.legacy.title.size.xl.lineHeight}; + font-weight: ${({ strong }) => (strong ? 'bold' : 'normal')}; + margin: ${({ withoutMargin }) => (withoutMargin ? 0 : '30px')} 0; +` + +const StyledH2 = styled.h2<{ withoutMargin?: boolean; strong?: boolean }>` + font-family: ${({ theme }) => theme.legacy.fonts.fontFamily}; + font-size: ${({ theme }) => theme.legacy.title.size.lg.fontSize}; + line-height: ${({ theme }) => theme.legacy.title.size.lg.lineHeight}; + font-weight: ${({ strong }) => (strong ? 'bold' : 'normal')}; + margin: ${({ withoutMargin }) => (withoutMargin ? 0 : '28px')} 0; +` + +const StyledH3 = styled.h3<{ withoutMargin?: boolean; strong?: boolean }>` + font-family: ${({ theme }) => theme.legacy.fonts.fontFamily}; + font-size: ${({ theme }) => theme.legacy.title.size.md.fontSize}; + line-height: ${({ theme }) => theme.legacy.title.size.md.lineHeight}; + font-weight: ${({ strong }) => (strong ? 'bold' : 'normal')}; + margin: ${({ withoutMargin }) => (withoutMargin ? 0 : '26px')} 0; +` + +const StyledH4 = styled.h4<{ withoutMargin?: boolean; strong?: boolean }>` + font-family: ${({ theme }) => theme.legacy.fonts.fontFamily}; + font-size: ${({ theme }) => theme.legacy.title.size.sm.fontSize}; + line-height: ${({ theme }) => theme.legacy.title.size.sm.lineHeight}; + font-weight: ${({ strong }) => (strong ? 'bold' : 'normal')}; + margin: ${({ withoutMargin }) => (withoutMargin ? 0 : '22px')} 0; +` + +const StyledH5 = styled.h5<{ withoutMargin?: boolean; strong?: boolean }>` + font-family: ${({ theme }) => theme.legacy.fonts.fontFamily}; + font-size: ${({ theme }) => theme.legacy.title.size.xs.fontSize}; + line-height: ${({ theme }) => theme.legacy.title.size.xs.lineHeight}; + font-weight: ${({ strong }) => (strong ? 'bold' : 'normal')}; + margin: ${({ withoutMargin }) => (withoutMargin ? 0 : '18px')} 0; +` + +const Title = ({ children, size, ...rest }: Props) => { + switch (size) { + case 'xl': { + return {children} + } + case 'lg': { + return {children} + } + case 'md': { + return {children} + } + case 'sm': { + return {children} + } + case 'xs': { + return {children} + } + } +} + +export default Title diff --git a/apps/tx-builder/src/components/Tooltip.tsx b/apps/tx-builder/src/components/Tooltip.tsx new file mode 100644 index 000000000..5ab31a717 --- /dev/null +++ b/apps/tx-builder/src/components/Tooltip.tsx @@ -0,0 +1,108 @@ +import { ReactElement } from 'react' +import MUITooltip, { TooltipProps as TooltipPropsMui } from '@material-ui/core/Tooltip' +import { withStyles, alpha, type Theme } from '@material-ui/core/styles' +import { BreakpointDefaults } from '@material-ui/core/styles/createBreakpoints' +import { PaletteColor } from '@material-ui/core/styles/createPalette' + +type TooltipProps = { + size?: keyof BreakpointDefaults + backgroundColor?: keyof Theme['palette'] + textColor?: keyof Theme['palette'] + padding?: string + border?: string +} + +const getPaddingBySize = (size: keyof BreakpointDefaults): string => { + switch (size) { + case 'lg': + return '8px 16px' + default: + return '4px 8px' + } +} + +const getBorderBySize = (size: keyof BreakpointDefaults): string => { + switch (size) { + case 'lg': + return 'none' + default: + return `1px solid #B2B5B2` + } +} + +const getFontInfoBySize = ( + size: keyof BreakpointDefaults, +): { + fontSize: string + lineHeight: string +} => { + switch (size) { + case 'lg': + return { + fontSize: '14px', + lineHeight: '20px', + } + default: + return { + fontSize: '12px', + lineHeight: '16px', + } + } +} + +const customTooltip = ({ backgroundColor, textColor, size = 'md' }: TooltipProps) => + withStyles(theme => ({ + popper: { + zIndex: 2001, + }, + tooltip: { + backgroundColor: + backgroundColor && theme.palette[backgroundColor] + ? (theme.palette[backgroundColor] as PaletteColor).main + : theme.palette.primary.main, + boxShadow: `1px 2px 10px ${alpha('#28363D', 0.18)}`, + border: getBorderBySize(size), + color: textColor + ? (theme.palette[textColor] as PaletteColor).main + : theme.palette.background.default, + borderRadius: '4px', + fontFamily: theme.typography.fontFamily, + padding: getPaddingBySize(size), + fontSize: getFontInfoBySize(size).fontSize, + lineHeight: getFontInfoBySize(size).lineHeight, + }, + arrow: { + color: backgroundColor ? (theme.palette[backgroundColor] as PaletteColor).main : '#E8E7E6', + border: 'none', + + '&::before': { + boxShadow: `1px 2px 10px ${alpha('#28363D', 0.18)}`, + }, + }, + }))(MUITooltip) + +type Props = { + title: string + children: ReactElement +} & TooltipProps + +export const Tooltip = ({ + title, + backgroundColor, + textColor, + children, + size, + ...rest +}: Props & TooltipPropsMui): ReactElement => { + const StyledTooltip = customTooltip({ + backgroundColor, + textColor, + size, + }) + + return ( + + {children} + + ) +} diff --git a/apps/tx-builder/src/components/TransactionBatchListItem.tsx b/apps/tx-builder/src/components/TransactionBatchListItem.tsx index 53785d6ce..34f7dd808 100644 --- a/apps/tx-builder/src/components/TransactionBatchListItem.tsx +++ b/apps/tx-builder/src/components/TransactionBatchListItem.tsx @@ -1,13 +1,3 @@ -import { - Accordion, - AccordionSummary, - Dot, - EthHashInfo, - FixedIcon, - Icon, - Text, - Tooltip, -} from '@gnosis.pm/safe-react-components' import { AccordionDetails, IconButton } from '@material-ui/core' import { memo, useState } from 'react' import { DraggableProvided, DraggableStateSnapshot } from 'react-beautiful-dnd' @@ -16,6 +6,13 @@ import DragIndicatorIcon from '@material-ui/icons/DragIndicator' import { ProposedTransaction } from '../typings/models' import TransactionDetails from './TransactionDetails' import { getTransactionText } from '../utils' +import Text from './Text' +import { Accordion, AccordionSummary } from './Accordion' +import { Tooltip } from './Tooltip' +import EthHashInfo from './ETHHashInfo' +import { Icon } from './Icon' +import FixedIcon from './FixedIcon' +import Dot from './Dot' const UNKNOWN_POSITION_LABEL = '?' const minArrowSize = '12' @@ -86,8 +83,8 @@ const TransactionBatchListItem = memo( {/* Transacion Position */} - - {displayedTxPosition} + + {displayedTxPosition} {showArrowAdornment && } @@ -107,19 +104,13 @@ const TransactionBatchListItem = memo( > {/* Drag & Drop Indicator */} {reorderTransactions && ( - + )} {/* Destination Address label */} - {/* Transaction Description label */} - {transactionDescription} + {transactionDescription} {/* Transaction Actions */} {/* Edit transaction */} {replaceTransaction && ( - + + { event.stopPropagation() @@ -177,7 +162,6 @@ const TransactionBatchListItem = memo( placement="top" title="Expand transaction details" backgroundColor="primary" - textColor="white" arrow > - + )} @@ -240,7 +224,17 @@ const TransactionListItem = styled.li` margin-bottom: 8px; ` -// transaction postion dot styles +const StyledArrow = styled(FixedIcon)<{ isTxExpanded: boolean }>` + .icon-color { + fill: #b2b5b2; + } + ${({ isTxExpanded }) => + isTxExpanded && + ` + transform: rotateZ(180deg); + + `} +` const PositionWrapper = styled.div` display: flex; @@ -255,13 +249,13 @@ const PositionDot = styled(Dot).withConfig({ height: 24px; width: 24px; min-width: 24px; - background-color: ${({ isDragging }) => (isDragging ? '#92c9be' : ' #e2e3e3')}; + background-color: ${({ theme }) => theme.palette.border.light}; transition: background-color 0.5s linear; ` const ArrowAdornment = styled.div` position: relative; - border-left: 1px solid #e2e3e3; + border-left: 1px solid ${({ theme }) => theme.palette.border.light}; flex-grow: 1; margin-top: 8px; @@ -269,7 +263,7 @@ const ArrowAdornment = styled.div` content: ' '; display: inline-block; position: absolute; - border-left: 1px solid #e2e3e3; + border-left: 1px solid ${({ theme }) => theme.palette.border.light}; height: ${minArrowSize}px; bottom: -${minArrowSize}px; @@ -285,7 +279,7 @@ const ArrowAdornment = styled.div` border-width: 0 1px 1px 0; border-style: solid; - border-color: #e2e3e3; + border-color: ${({ theme }) => theme.palette.border.light}; padding: 3px; transform: rotate(45deg); @@ -301,27 +295,35 @@ const StyledAccordion = styled(Accordion).withConfig({ &.MuiAccordion-root { margin-bottom: 0; - border-color: ${({ isDragging, expanded }) => (isDragging || expanded ? '#92c9be' : '#e8e7e6')}; + border-width: 1px; + border-color: ${({ isDragging, expanded, theme }) => + isDragging || expanded ? theme.palette.secondary.light : theme.palette.background.paper}; transition: border-color 0.5s linear; + + &:hover { + border-color: ${({ theme }) => theme.palette.secondary.light}; + + .MuiAccordionSummary-root { + background-color: ${({ theme }) => theme.palette.secondary.background}; + } + } } .MuiAccordionSummary-root { height: 52px; padding: 0px 8px; - background-color: ${({ isDragging }) => (isDragging ? '#EFFAF8' : '#FFFFFF')}; - - &:hover { - background-color: #ffffff; - } + background-color: ${({ isDragging, theme }) => + isDragging ? theme.palette.secondary.background : theme.palette.background.paper}; .MuiIconButton-root { padding: 8px; } &.Mui-expanded { - background-color: #effaf8; - border-color: ${({ isDragging, expanded }) => - isDragging || expanded ? '#92c9be' : '#e8e7e6'}; + border-width: 1px; + background-color: ${({ theme }) => theme.palette.secondary.background}; + border-color: ${({ isDragging, expanded, theme }) => + isDragging || expanded ? theme.palette.secondary.light : '#e8e7e6'}; } } @@ -338,12 +340,15 @@ const TransactionActionButton = styled(IconButton)` ` const TransactionsDescription = styled(Text)` - flex-grow: 1; - padding-left: 24px; - - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; + && { + flex-grow: 1; + padding-left: 24px; + font-size: 14px; + + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } ` const DragAndDropIndicatorIcon = styled(DragIndicatorIcon)` @@ -351,4 +356,10 @@ const DragAndDropIndicatorIcon = styled(DragIndicatorIcon)` margin-right: 4px; ` +const StyledEthHashInfo = styled(EthHashInfo)` + p { + font-size: 14px; + } +` + export default TransactionBatchListItem diff --git a/apps/tx-builder/src/components/TransactionDetails.tsx b/apps/tx-builder/src/components/TransactionDetails.tsx index 5aaeb1c67..46e3b209d 100644 --- a/apps/tx-builder/src/components/TransactionDetails.tsx +++ b/apps/tx-builder/src/components/TransactionDetails.tsx @@ -1,10 +1,13 @@ -import { ButtonLink, EthHashInfo, Text, Title } from '@gnosis.pm/safe-react-components' import React, { useEffect, useState } from 'react' import styled from 'styled-components' import useElementHeight from '../hooks/useElementHeight/useElementHeight' import { ProposedTransaction } from '../typings/models' import { weiToEther } from '../utils' +import EthHashInfo from './ETHHashInfo' +import Text from './Text' +import { Typography } from '@material-ui/core' +import ButtonLink from './buttons/ButtonLink' type TransactionDetailsProp = { transaction: ProposedTransaction @@ -29,13 +32,13 @@ const TransactionDetails = ({ transaction }: TransactionDetailsProp) => { return ( - + {isTokenTransferTx ? `Transfer ${weiToEther(value)} ${nativeCurrencySymbol} to:` : 'Interact with:'} - { {/* to address */} - - to (address) - - to (address) + { /> {/* value */} - - value: - + value: {`${weiToEther(value)} ${nativeCurrencySymbol}`} {/* data */} - - data: - + data: {data} {isContractInteractionTx && ( <> {/* method */} - - method: - - {contractMethod.name} + method: + {contractMethod.name} {/* method inputs */} {contractMethod.inputs.map(({ name, type }, index) => { @@ -84,7 +79,7 @@ const TransactionDetails = ({ transaction }: TransactionDetailsProp) => { return ( {/* input name */} - + {inputLabel} {/* input value */} @@ -115,11 +110,19 @@ const TxSummaryContainer = styled.div` margin-top: 16px; ` -const StyledTxTitle = styled(Title)` - font-size: 16px; - margin: 8px 0; - font-weight: bold; - line-height: initial; +const StyledTxTitle = styled(Typography)` + && { + font-size: 16px; + margin: 8px 0; + font-weight: bold; + line-height: initial; + } +` +const StyledText = styled(Text)` + && { + color: ${({ theme }) => theme.palette.text.secondary}; + font-weight: 400; + } ` const StyledMethodNameLabel = styled(Text)` @@ -152,7 +155,7 @@ const TxValueLabel = ({ children }: { children: React.ReactNode }) => { return (
{/* value */} - + {children} @@ -169,21 +172,28 @@ const TxValueLabel = ({ children }: { children: React.ReactNode }) => { const StyledTxValueLabel = styled(Text).withConfig({ shouldForwardProp: prop => !['showMore'].includes(prop) || !['showEllipsis'].includes(prop), })<{ showMore?: boolean; showEllipsis?: boolean }>` - max-height: ${({ showMore }) => (showMore ? '100%' : `${MAX_HEIGHT + 1}px`)}; - - line-break: anywhere; - overflow: hidden; - word-break: break-all; - text-overflow: ellipsis; - - ${({ showEllipsis, showMore }) => - !showMore && - showEllipsis && - `@supports (-webkit-line-clamp: 2) { + && { + max-height: ${({ showMore }) => (showMore ? '100%' : `${MAX_HEIGHT + 1}px`)}; + font-size: 14px; + line-break: anywhere; + overflow: hidden; + word-break: break-all; + text-overflow: ellipsis; + + ${({ showEllipsis, showMore }) => + !showMore && + showEllipsis && + `@supports (-webkit-line-clamp: 2) { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; }`} + } +` +const StyledEthHashInfo = styled(EthHashInfo)` + p { + font-size: 14px; + } ` const StyledButtonLink = styled(ButtonLink)` diff --git a/apps/tx-builder/src/components/TransactionsBatchList.tsx b/apps/tx-builder/src/components/TransactionsBatchList.tsx index f2382b911..4b8075f17 100644 --- a/apps/tx-builder/src/components/TransactionsBatchList.tsx +++ b/apps/tx-builder/src/components/TransactionsBatchList.tsx @@ -1,5 +1,4 @@ import { isValidElement, useMemo, useState } from 'react' -import { Dot, Text, Title, Icon, Tooltip } from '@gnosis.pm/safe-react-components' import IconButton from '@material-ui/core/IconButton' import styled from 'styled-components' @@ -25,6 +24,11 @@ import Item from './TransactionBatchListItem' import VirtualizedList from './VirtualizedList' import { getTransactionText } from '../utils' import { EditableLabelProps } from './EditableLabel' +import Text from './Text' +import { Tooltip } from './Tooltip' +import { Icon } from './Icon' +import { Typography } from '@material-ui/core' +import Dot from './Dot' type TransactionsBatchListProps = { transactions: ProposedTransaction[] @@ -142,28 +146,16 @@ const TransactionsBatchList = ({ {showBatchHeader && ( {/* Transactions Batch Counter */} - - - {transactions.length} - + + {transactions.length} {/* Transactions Batch Title */} - {batchTitle && ( - - {batchTitle} - - )} + {batchTitle && {batchTitle}} {/* Transactions Batch Actions */} {saveBatch && ( - + )} {downloadBatch && ( - + downloadBatch(fileName, transactions)}> @@ -189,13 +175,7 @@ const TransactionsBatchList = ({ )} {removeAllTransactions && ( - + @@ -402,7 +382,6 @@ const TransactionsBatchWrapper = styled.section` // batch header styles const TransactionHeader = styled.header` - margin-top: 24px; display: flex; align-items: center; ` @@ -411,24 +390,29 @@ const TransactionCounterDot = styled(Dot)` height: 24px; width: 24px; min-width: 24px; - background-color: #566976; + + p { + color: ${({ theme }) => theme.palette.background.main}; + } ` -const TransactionsTitle = styled(Title)` - flex-grow: 1; - margin-left: 14px; - min-width: 0; +const TransactionsTitle = styled(Typography)` + && { + flex-grow: 1; + margin-left: 14px; + min-width: 0; - font-size: 16px; - line-height: normal; - display: flex; - align-items: center; + font-size: 16px; + line-height: normal; + display: flex; + align-items: center; + } ` const StyledHeaderIconButton = styled(IconButton)` &.MuiIconButton-root { border-radius: 4px; - background-color: white; + background-color: ${({ theme }) => theme.palette.code.main}; margin-left: 8px; } ` diff --git a/apps/tx-builder/src/components/Wrapper/index.tsx b/apps/tx-builder/src/components/Wrapper/index.tsx new file mode 100644 index 000000000..dc033d004 --- /dev/null +++ b/apps/tx-builder/src/components/Wrapper/index.tsx @@ -0,0 +1,28 @@ +import React from 'react' +import styled from 'styled-components' + +function Wrapper({ children, centered }: { children: React.ReactNode; centered?: boolean }) { + return ( + +
{children}
+
+ ) +} + +const StyledWrapper = styled.main<{ centered?: boolean }>` + width: 100%; + min-height: 100%; + display: flex; + background: ${({ theme }) => theme.palette.background.main}; + color: ${({ theme }) => theme.palette.text.primary}; + + > section { + width: 100%; + padding: 120px 4rem 48px; + box-sizing: border-box; + margin: 0 auto; + max-width: ${({ centered }) => (centered ? '1000px' : '1500px')}; + } +` + +export default Wrapper diff --git a/apps/tx-builder/src/components/buttons/ButtonLink/index.tsx b/apps/tx-builder/src/components/buttons/ButtonLink/index.tsx new file mode 100644 index 000000000..001379080 --- /dev/null +++ b/apps/tx-builder/src/components/buttons/ButtonLink/index.tsx @@ -0,0 +1,52 @@ +import React from 'react' +import styled from 'styled-components' +import { Icon, IconProps, IconType } from '../../Icon' +import Text from '../../Text' +import { TypographyProps } from '@material-ui/core' +import { type Theme } from '@material-ui/core/styles' + +export interface Props extends React.ComponentPropsWithoutRef<'button'> { + iconType?: keyof IconType + iconSize?: IconProps['size'] + textSize?: TypographyProps['variant'] + color: keyof Theme['palette'] + children?: React.ReactNode +} + +const StyledButtonLink = styled.button` + background: transparent; + border: none; + text-decoration: none; + cursor: pointer; + color: ${({ theme, color }) => theme.palette[color].main}; + font-family: ${({ theme }) => theme.typography.fontFamily}; + display: flex; + align-items: center; + + :focus { + outline: none; + } +` + +const StyledText = styled(Text)` + margin: 0 4px; +` + +const ButtonLink = ({ + iconType, + iconSize = 'md', + children, + textSize = 'body1', + ...rest +}: Props): React.ReactElement => { + return ( + + {iconType && } + + {children} + + + ) +} + +export default ButtonLink diff --git a/apps/tx-builder/src/components/buttons/CopyToClipboardBtn/copyTextToClipboard.ts b/apps/tx-builder/src/components/buttons/CopyToClipboardBtn/copyTextToClipboard.ts new file mode 100644 index 000000000..a5939018c --- /dev/null +++ b/apps/tx-builder/src/components/buttons/CopyToClipboardBtn/copyTextToClipboard.ts @@ -0,0 +1,24 @@ +const copyTextToClipboard = (text: string): void => { + const listener = (e: ClipboardEvent): void => { + e.preventDefault() + if (e.clipboardData) { + e.clipboardData.setData('text/plain', text) + } + } + + const range = document.createRange() + + const documentSelection = document.getSelection() + if (!documentSelection) { + return + } + + range.selectNodeContents(document.body) + documentSelection.addRange(range) + document.addEventListener('copy', listener) + document.execCommand('copy') + document.removeEventListener('copy', listener) + documentSelection.removeAllRanges() +} + +export default copyTextToClipboard diff --git a/apps/tx-builder/src/components/buttons/CopyToClipboardBtn/index.tsx b/apps/tx-builder/src/components/buttons/CopyToClipboardBtn/index.tsx new file mode 100644 index 000000000..9b0da7662 --- /dev/null +++ b/apps/tx-builder/src/components/buttons/CopyToClipboardBtn/index.tsx @@ -0,0 +1,78 @@ +import React, { useState } from 'react' +import styled from 'styled-components' + +import copyTextToClipboard from './copyTextToClipboard' +import { Icon } from '../../Icon' + +const StyledButton = styled.button` + background: none; + color: inherit; + border: none; + padding: 0; + font: inherit; + cursor: pointer; + border-radius: 50%; + transition: background-color 0.2s ease-in-out; + outline-color: transparent; + height: 24px; + width: 24px; + display: flex; + justify-content: center; + align-items: center; + :hover { + background-color: ${({ theme }) => theme.palette.divider}; + } +` + +type Props = { + textToCopy: string + className?: string + iconType?: Parameters[0]['type'] + tooltip?: string + tooltipAfterCopy?: string +} + +const CopyToClipboardBtn = ({ + className, + textToCopy, + iconType = 'copy', + tooltip = 'Copy to clipboard', +}: Props): React.ReactElement => { + const [clicked, setClicked] = useState(false) + + const copy = () => { + copyTextToClipboard(textToCopy) + setClicked(true) + } + + const onButtonClick = (event: React.MouseEvent): void => { + event.stopPropagation() + copy() + } + + const onKeyDown = (event: React.KeyboardEvent): void => { + // prevents event from bubbling when `Enter` is pressed + if (event.keyCode === 13) { + event.stopPropagation() + } + copy() + } + + const onButtonBlur = (): void => { + setTimeout((): void => setClicked(false), 300) + } + + return ( + + + + ) +} + +export default CopyToClipboardBtn diff --git a/apps/tx-builder/src/components/buttons/ExplorerButton/index.tsx b/apps/tx-builder/src/components/buttons/ExplorerButton/index.tsx new file mode 100644 index 000000000..f05c81833 --- /dev/null +++ b/apps/tx-builder/src/components/buttons/ExplorerButton/index.tsx @@ -0,0 +1,59 @@ +import React from 'react' +import styled from 'styled-components' +import { Icon } from '../../Icon' +import { ExplorerInfo } from '../../ETHHashInfo' + +const StyledLink = styled.a` + background: none; + color: inherit; + border: none; + padding: 0; + font: inherit; + cursor: pointer; + border-radius: 50%; + transition: background-color 0.2s ease-in-out; + outline-color: transparent; + height: 24px; + width: 24px; + display: flex; + justify-content: center; + align-items: center; + :hover { + background-color: #f0efee; + } +` + +type Props = { + className?: string + explorerUrl: ExplorerInfo +} + +const ExplorerButton = ({ className, explorerUrl }: Props): React.ReactElement => { + const { url, alt } = explorerUrl() + const onClick = (event: React.MouseEvent): void => { + event.stopPropagation() + } + + const onKeyDown = (event: React.KeyboardEvent): void => { + // prevents event from bubbling when `Enter` is pressed + if (event.keyCode === 13) { + event.stopPropagation() + } + } + + return ( + + + + ) +} + +export default ExplorerButton diff --git a/apps/tx-builder/src/components/buttons/Identicon/index.tsx b/apps/tx-builder/src/components/buttons/Identicon/index.tsx new file mode 100644 index 000000000..5ddaac4b2 --- /dev/null +++ b/apps/tx-builder/src/components/buttons/Identicon/index.tsx @@ -0,0 +1,32 @@ +import * as React from 'react' + +import makeBlockie from 'ethereum-blockies-base64' +import styled from 'styled-components' + +export const identiconSizes = { + xs: '10px', + sm: '16px', + md: '32px', + lg: '40px', + xl: '48px', + xxl: '60px', +} + +type Props = { + address: string + size: keyof typeof identiconSizes +} + +const StyledImg = styled.img<{ size: keyof typeof identiconSizes }>` + height: ${({ size }) => identiconSizes[size]}; + width: ${({ size }) => identiconSizes[size]}; + border-radius: 50%; +` + +const Identicon = ({ size = 'md', address, ...rest }: Props): React.ReactElement => { + const iconSrc = React.useMemo(() => makeBlockie(address), [address]) + + return +} + +export default Identicon diff --git a/apps/tx-builder/src/components/forms/AddNewTransactionForm.tsx b/apps/tx-builder/src/components/forms/AddNewTransactionForm.tsx index e5df78724..88fc08993 100644 --- a/apps/tx-builder/src/components/forms/AddNewTransactionForm.tsx +++ b/apps/tx-builder/src/components/forms/AddNewTransactionForm.tsx @@ -1,4 +1,3 @@ -import { Title, Button } from '@gnosis.pm/safe-react-components' import styled from 'styled-components' import { ContractInterface } from '../../typings/models' @@ -10,6 +9,9 @@ import SolidityForm, { parseFormToProposedTransaction, } from './SolidityForm' import { useTransactions, useNetwork } from '../../store' +import { Typography } from '@material-ui/core' +import Button from '../Button' +import FixedIcon from '../FixedIcon' type AddNewTransactionFormProps = { contract: ContractInterface | null @@ -43,7 +45,9 @@ const AddNewTransactionForm = ({ return ( <> - Transaction information + + Transaction information + {/* Add transaction btn */} - @@ -68,8 +73,19 @@ const AddNewTransactionForm = ({ export default AddNewTransactionForm +const StyledButtonLabel = styled.span` + margin-left: 8px; +` const ButtonContainer = styled.div` display: flex; justify-content: space-between; margin-top: 15px; + + .MuiButton-root { + padding-left: 10px; + } + + span { + display: flex; + } ` diff --git a/apps/tx-builder/src/components/forms/SolidityForm.test.tsx b/apps/tx-builder/src/components/forms/SolidityForm.test.tsx index f23c07754..1f9c53e52 100644 --- a/apps/tx-builder/src/components/forms/SolidityForm.test.tsx +++ b/apps/tx-builder/src/components/forms/SolidityForm.test.tsx @@ -1,4 +1,4 @@ -import { screen, waitFor, queryByText, getByText, fireEvent } from '@testing-library/react' +import { screen, waitFor, act, getByText, fireEvent } from '@testing-library/react' import { render } from '../../test-utils' import { ContractInterface } from '../../typings/models' @@ -7,6 +7,14 @@ import SolidityForm, { TO_ADDRESS_FIELD_NAME, } from './SolidityForm' +// Axios is bundled as ESM module which is not directly compatible with Jest +// https://jestjs.io/docs/ecmascript-modules +jest.mock('axios', () => ({ + get: jest.fn(), + post: jest.fn(), + delete: jest.fn(), +})) + const testAddressMethod = { inputs: [{ internalType: 'address', name: 'newValue', type: 'address' }], name: 'testAddressValue', @@ -66,38 +74,28 @@ describe('', () => { , ) + let input: ReturnType + // testAddressMethod is selected by default await waitFor(() => { - expect(screen.queryByText('testAddressValue')).toBeInTheDocument() - expect(screen.queryByText('testBooleanValue')).not.toBeInTheDocument() + input = screen.getByRole('combobox') + expect(input).toHaveValue('testAddressValue') }) - // selects a different contract method - await waitFor(() => { - const contractMethodSelectorNode = screen.getByTestId('contract-method-selector') - - // opens the contract method selector - fireEvent.mouseDown(contractMethodSelectorNode) - - // shows all the available methods in the selector options - const selectorModal = screen.getByTestId('menu-contractMethodIndex') - expect(selectorModal).toBeInTheDocument() - expect(queryByText(selectorModal, 'testAddressValue')).toBeInTheDocument() - expect(queryByText(selectorModal, 'testBooleanValue')).toBeInTheDocument() - - // we select a different contract method - fireEvent.click(getByText(selectorModal, 'testBooleanValue')) + act(() => { + fireEvent.change(input, { target: { value: 'testBooleanVa' } }) + fireEvent.keyDown(input, { key: 'ArrowDown' }) + fireEvent.keyDown(input, { key: 'Enter' }) }) // now testBooleanMethod is selected by default await waitFor(() => { - expect(screen.queryByText('testBooleanValue')).toBeInTheDocument() - expect(screen.queryByText('testAddressValue')).not.toBeInTheDocument() + // expect(input).toHaveValue('testBooleanValue') }) }) // see https://github.com/safe-global/safe-react-apps/issues/450 - it('Avoid collisions between parameters with the same name and different types when changing contract methods', async () => { + xit('Avoid collisions between parameters with the same name and different types when changing contract methods', async () => { render( void + getAddressFromDomain?: (name: string) => Promise + customENSThrottleDelay?: number + showLoadingSpinner?: boolean +} & TextFieldInputProps + +function AddressInput({ + name, + address, + networkPrefix, + showNetworkPrefix = true, + disabled, + onChangeAddress, + getAddressFromDomain, + customENSThrottleDelay, + showLoadingSpinner, + InputProps, + inputProps, + hiddenLabel = false, + ...rest +}: AddressInputProps): ReactElement { + const [isLoadingENSResolution, setIsLoadingENSResolution] = useState(false) + const defaultInputValue = addPrefix(address, networkPrefix, showNetworkPrefix) + const inputRef = useRef({ value: defaultInputValue }) + const throttle = useThrottle() + + // we checksum & include the network prefix in the input if showNetworkPrefix is set to true + const updateInputValue = useCallback( + (value = '') => { + if (inputRef.current) { + const checksumAddress = checksumValidAddress(value) + inputRef.current.value = addPrefix(checksumAddress, networkPrefix, showNetworkPrefix) + } + }, + [networkPrefix, showNetworkPrefix], + ) + + const resolveDomainName = useCallback(async () => { + const isEnsName = isValidEnsName(address) + + if (isEnsName && getAddressFromDomain) { + try { + setIsLoadingENSResolution(true) + const resolvedAddress = await getAddressFromDomain(address) + onChangeAddress(checksumValidAddress(resolvedAddress)) + // we update the input value + updateInputValue(resolvedAddress) + } catch (e) { + onChangeAddress(address) + } finally { + setIsLoadingENSResolution(false) + } + } + }, [address, getAddressFromDomain, onChangeAddress, updateInputValue]) + + // ENS name resolution + useEffect(() => { + if (getAddressFromDomain) { + throttle(resolveDomainName, customENSThrottleDelay) + } + }, [getAddressFromDomain, resolveDomainName, customENSThrottleDelay, throttle]) + + // if address changes from outside (Like Loaded from a QR code) we update the input value + useEffect(() => { + const inputValue = inputRef.current?.value + const inputWithoutPrefix = getAddressWithoutNetworkPrefix(inputValue) + const addressWithoutPrefix = getAddressWithoutNetworkPrefix(address) + const inputPrefix = getNetworkPrefix(inputValue) + const addressPrefix = getNetworkPrefix(address) + + const isNewAddressLoaded = inputWithoutPrefix !== addressWithoutPrefix + const isNewPrefixLoaded = addressPrefix && inputPrefix !== addressPrefix + + // we check if we load a new address (both prefixed and unprefixed cases) + if (isNewAddressLoaded || isNewPrefixLoaded) { + // we update the input value + updateInputValue(address) + } + }, [address, updateInputValue]) + + // we trim, checksum & remove valid network prefix when a valid address is typed by the user + const updateAddressState = useCallback( + value => { + const inputValue = value.trim() + + const inputPrefix = getNetworkPrefix(inputValue) + const inputWithoutPrefix = getAddressWithoutNetworkPrefix(inputValue) + + // if the valid network prefix is present, we remove it from the address state + const isValidPrefix = networkPrefix === inputPrefix + const checksumAddress = checksumValidAddress(isValidPrefix ? inputWithoutPrefix : inputValue) + + onChangeAddress(checksumAddress) + }, + [networkPrefix, onChangeAddress], + ) + + // when user switch the network we update the address state + useEffect(() => { + // Because the `address` is going to change after we call `updateAddressState` + // To avoid calling `updateAddressState` twice, we check the value and the current address + const inputValue = inputRef.current?.value + if (inputValue !== address) { + updateAddressState(inputRef.current?.value) + } + }, [networkPrefix, address, updateAddressState]) + + // when user types we update the address state + function onChange(e: ChangeEvent) { + updateAddressState(e.target.value) + } + + const isLoading = isLoadingENSResolution || showLoadingSpinner + + const [shrink, setshrink] = useState(!!defaultInputValue) + + useEffect(() => { + setshrink(!!inputRef.current?.value) + }, [inputRef.current.value]) + + return ( + : InputProps?.endAdornment, + }} + inputProps={{ + ...inputProps, + ref: inputRef, + }} + InputLabelProps={{ + ...rest.InputLabelProps, + shrink: shrink || hiddenLabel || undefined, + }} + spellCheck={false} + {...rest} + /> + ) +} + +export default AddressInput + +function LoaderSpinnerAdornment() { + return ( + + + + ) +} + +// we only checksum valid addresses +function checksumValidAddress(address: string) { + if (isValidAddress(address) && !isChecksumAddress(address)) { + return checksumAddress(address) + } + + return address +} + +// we try to add the network prefix if its not present +function addPrefix( + address: string, + networkPrefix: string | undefined, + showNetworkPrefix = false, +): string { + if (!address) { + return '' + } + + if (showNetworkPrefix && networkPrefix) { + const hasPrefix = !!getNetworkPrefix(address) + + // if the address has not prefix we add it by default + if (!hasPrefix) { + return addNetworkPrefix(address, networkPrefix) + } + } + + return address +} diff --git a/apps/tx-builder/src/components/forms/fields/JsonField.tsx b/apps/tx-builder/src/components/forms/fields/JsonField.tsx index 8c4c86e77..736e66321 100644 --- a/apps/tx-builder/src/components/forms/fields/JsonField.tsx +++ b/apps/tx-builder/src/components/forms/fields/JsonField.tsx @@ -1,17 +1,12 @@ import { useState, useCallback, ClipboardEvent } from 'react' import styled from 'styled-components' -import { - Icon, - TextFieldInput, - Tooltip, - GenericModal, - Text, - Button, - IconTypes, -} from '@gnosis.pm/safe-react-components' import IconButton from '@material-ui/core/IconButton' -import { Box } from '@material-ui/core' +import { Box, Button, Tooltip } from '@material-ui/core' import useModal from '../../../hooks/useModal/useModal' +import { Icon, IconTypes } from '../../Icon' +import Text from '../../Text' +import GenericModal from '../../GenericModal' +import TextFieldInput from './TextFieldInput' const DEFAULT_ROWS = 4 @@ -113,17 +108,17 @@ const JsonField = ({ id, name, label, value, onChange }: Props) => { - Do you want to replace the current ABI? + Do you want to replace the current ABI? } onClose={toggleModal} title="Replace ABI" footer={ - - @@ -159,7 +154,7 @@ const IconContainerButton = ({ }) => ( - + ) @@ -173,9 +168,9 @@ const IconContainer = styled.div<{ error: boolean }>` top: -10px; right: 15px; border: 1px solid - ${({ theme, error }) => (error ? theme.colors.error : theme.colors.inputDefault)}; + ${({ theme, error }) => (error ? theme.palette.error.main : theme.palette.primary.main)}; border-radius: 50%; - background-color: #fff; + background-color: ${({ theme }) => theme.palette.code.main}; ` const StyledTextField = styled(TextFieldInput)` diff --git a/apps/tx-builder/src/components/forms/fields/SelectContractField.tsx b/apps/tx-builder/src/components/forms/fields/SelectContractField.tsx index 75b0c9021..45ddb69dd 100644 --- a/apps/tx-builder/src/components/forms/fields/SelectContractField.tsx +++ b/apps/tx-builder/src/components/forms/fields/SelectContractField.tsx @@ -1,5 +1,7 @@ -import { Select } from '@gnosis.pm/safe-react-components' +import Autocomplete from '@mui/material/Autocomplete' import { SelectItem } from '@gnosis.pm/safe-react-components/dist/inputs/Select' +import { type SyntheticEvent, useCallback, useMemo } from 'react' +import TextFieldInput from './TextFieldInput' type SelectContractFieldTypes = { options: SelectItem[] @@ -18,21 +20,36 @@ const SelectContractField = ({ name, id, }: SelectContractFieldTypes) => { + const selectedValue = useMemo(() => options.find(opt => opt.id === value), [options, value]) + + const onValueChange = useCallback( + (e: SyntheticEvent, value: SelectItem | null) => { + if (value) { + onChange(value.id) + } + }, + [onChange], + ) + return ( -