├── .nvmrc ├── cypress ├── ci.json ├── fixtures │ ├── rinkeby_safe_QR.png │ └── address_book_test.csv ├── support │ └── index.js └── integration │ └── create_safe.spec.js ├── src ├── logic │ ├── safe │ │ ├── utils │ │ │ ├── index.ts │ │ │ ├── mocks │ │ │ │ ├── getChainsConfigMock.ts │ │ │ │ ├── mockTokenCurrenciesBalancesResponse.ts │ │ │ │ └── getSafeMock.ts │ │ │ ├── guardManager.ts │ │ │ ├── safeInformation.ts │ │ │ └── shouldSafeStoreBeUpdated.ts │ │ ├── transactions │ │ │ ├── index.ts │ │ │ ├── notifiedTransactions.ts │ │ │ ├── api │ │ │ │ └── fetchSafeTransaction.ts │ │ │ └── awaitingTransactions.ts │ │ ├── store │ │ │ ├── actions │ │ │ │ ├── clearSafeList.ts │ │ │ │ ├── removeSafe.ts │ │ │ │ ├── setLastOpenedSafe.ts │ │ │ │ ├── updateSafe.ts │ │ │ │ ├── types.d.ts │ │ │ │ ├── addOrUpdateSafe.ts │ │ │ │ ├── setLatestMasterContractVersion.ts │ │ │ │ ├── loadSafesFromStorage.ts │ │ │ │ ├── fetchLatestMasterContractVersion.ts │ │ │ │ └── transactions │ │ │ │ │ ├── gatewayTransactions.ts │ │ │ │ │ └── utils │ │ │ │ │ └── transactionHelpers.ts │ │ │ ├── models │ │ │ │ ├── types │ │ │ │ │ ├── confirmation.ts │ │ │ │ │ └── transaction.ts │ │ │ │ └── confirmation.ts │ │ │ ├── selectors │ │ │ │ └── utils.ts │ │ │ └── reducer │ │ │ │ └── types │ │ │ │ └── safe.ts │ │ ├── api │ │ │ ├── fetchSafeApps.ts │ │ │ ├── fetchSafesByOwner.ts │ │ │ ├── __tests__ │ │ │ │ └── fetchTokenCurrenciesBalances.test.ts │ │ │ └── fetchTokenCurrenciesBalances.ts │ │ └── hooks │ │ │ ├── useTokenInfo.tsx │ │ │ └── useSafeScheduledUpdates.tsx │ ├── notifications │ │ └── index.ts │ ├── wallets │ │ ├── transactionDataCheck │ │ │ └── images │ │ │ │ └── ledger.jpg │ │ ├── store │ │ │ └── actions │ │ │ │ ├── index.ts │ │ │ │ ├── updateProviderEns.ts │ │ │ │ ├── updateProviderWallet.ts │ │ │ │ ├── updateProviderAccount.ts │ │ │ │ └── updateProviderNetwork.ts │ │ ├── utils │ │ │ └── unstoppableDomains.ts │ │ ├── errors.ts │ │ └── e2e-wallet │ │ │ └── module.ts │ ├── config │ │ ├── store │ │ │ ├── reducer │ │ │ │ ├── reducer.d.ts │ │ │ │ └── index.ts │ │ │ ├── actions │ │ │ │ └── index.ts │ │ │ └── selectors │ │ │ │ └── index.ts │ │ └── utils │ │ │ └── index.ts │ ├── tokens │ │ ├── store │ │ │ ├── actions │ │ │ │ ├── addTokens.ts │ │ │ │ └── fetchTokens.ts │ │ │ ├── selectors │ │ │ │ └── index.ts │ │ │ ├── model │ │ │ │ └── token.ts │ │ │ └── reducer │ │ │ │ └── tokens.ts │ │ ├── api │ │ │ └── fetchSafeCollectibles.ts │ │ └── utils │ │ │ └── humanReadableValue.ts │ ├── currentSession │ │ ├── store │ │ │ ├── actions │ │ │ │ ├── clearCurrentSession.ts │ │ │ │ ├── removeViewedSafe.ts │ │ │ │ ├── updateViewedSafes.ts │ │ │ │ ├── loadCurrentSession.ts │ │ │ │ ├── addCurrentShortName.ts │ │ │ │ ├── addCurrentSafeAddress.ts │ │ │ │ ├── addViewedSafe.ts │ │ │ │ └── loadCurrentSessionFromStorage.ts │ │ │ └── selectors │ │ │ │ └── index.ts │ │ ├── utils │ │ │ └── index.ts │ │ └── hooks │ │ │ └── useSafeAddress.ts │ ├── cookies │ │ ├── store │ │ │ ├── selectors │ │ │ │ └── index.ts │ │ │ ├── actions │ │ │ │ └── openCookieBanner.ts │ │ │ └── reducer │ │ │ │ └── cookies.ts │ │ └── model │ │ │ └── cookie.ts │ ├── hooks │ │ ├── useQuery.ts │ │ ├── useDarkMode.ts │ │ ├── useStateHandler.tsx │ │ ├── useRecommendedNonce.tsx │ │ ├── useWindowDimensions.tsx │ │ ├── useSafeAppUrl.tsx │ │ ├── useIsSmartContractWallet.ts │ │ ├── useEstimationStatus.tsx │ │ ├── useTxStatus.ts │ │ └── useMnemonicName.ts │ ├── appearance │ │ ├── actions │ │ │ ├── setCopyShortName.ts │ │ │ └── setShowShortName.ts │ │ └── selectors │ │ │ └── index.ts │ ├── currencyValues │ │ └── store │ │ │ ├── actions │ │ │ ├── setSelectedCurrency.ts │ │ │ ├── setAvailableCurrencies.ts │ │ │ └── updateAvailableCurrencies.ts │ │ │ └── selectors │ │ │ └── index.ts │ ├── collectibles │ │ ├── sources │ │ │ └── index.ts │ │ └── store │ │ │ └── actions │ │ │ ├── addCollectibles.ts │ │ │ └── fetchCollectibles.ts │ ├── addressBook │ │ └── model │ │ │ └── addressBook.ts │ └── contracts │ │ └── __tests__ │ │ └── safeContractErrors.test.ts ├── react-app-env.d.ts ├── assets │ ├── logo.png │ ├── fonts │ │ ├── Averta-normal.woff2 │ │ └── Averta-ExtraBold.woff2 │ ├── icons │ │ ├── shape.svg │ │ ├── check_bg.svg │ │ ├── info.svg │ │ ├── alert.svg │ │ ├── check.svg │ │ ├── apps.svg │ │ ├── info_red.svg │ │ └── error.svg │ └── logo.svg ├── components │ ├── layout │ │ ├── Pre │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ │ ├── Typography │ │ │ └── index.ts │ │ ├── Img │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ │ ├── Bold │ │ │ └── index.tsx │ │ ├── Page │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ │ ├── Span │ │ │ ├── index.tsx │ │ │ └── Span.test.jsx │ │ ├── Divider │ │ │ └── index.tsx │ │ ├── Link │ │ │ └── index.module.scss │ │ ├── ButtonLink │ │ │ ├── ButtonLink.test.jsx │ │ │ └── index.tsx │ │ ├── Button │ │ │ ├── index.tsx │ │ │ └── GnoButton.test.tsx │ │ ├── Backdrop │ │ │ └── index.tsx │ │ ├── Row │ │ │ ├── index.module.scss │ │ │ └── index.tsx │ │ ├── Heading │ │ │ └── index.tsx │ │ ├── Hairline │ │ │ └── index.tsx │ │ ├── Block │ │ │ ├── index.tsx │ │ │ └── index.module.scss │ │ ├── Tooltip │ │ │ └── index.tsx │ │ ├── Table │ │ │ └── index.tsx │ │ └── Paragraph │ │ │ └── index.tsx │ ├── CookiesBanner │ │ └── assets │ │ │ ├── intercom.png │ │ │ └── alert-red.svg │ ├── AppLayout │ │ ├── MobileStart │ │ │ └── assets │ │ │ │ └── phone@2x.png │ │ ├── Header │ │ │ ├── assets │ │ │ │ ├── transition-logo.gif │ │ │ │ ├── dotRinkeby.svg │ │ │ │ ├── triangle.svg │ │ │ │ ├── key.svg │ │ │ │ └── wallet.svg │ │ │ └── components │ │ │ │ ├── WalletIcon │ │ │ │ └── icons │ │ │ │ │ ├── icon-opera.png │ │ │ │ │ ├── icon-authereum.png │ │ │ │ │ ├── icon-keystone.png │ │ │ │ │ ├── icon-metamask.png │ │ │ │ │ ├── icon-opera-touch.png │ │ │ │ │ ├── icon-squarelink.png │ │ │ │ │ ├── icon-fortmatic.svg │ │ │ │ │ ├── icon-coinbase.svg │ │ │ │ │ ├── icon-trezor.svg │ │ │ │ │ └── icon-safe-mobile.svg │ │ │ │ ├── CircleDot.tsx │ │ │ │ ├── ProviderDetails │ │ │ │ ├── HidePairingModule.tsx │ │ │ │ └── MobilePairing.tsx │ │ │ │ └── AnimatedLogo.tsx │ │ └── Sidebar │ │ │ ├── Threshold │ │ │ └── index.tsx │ │ │ └── DebugToggle │ │ │ └── index.tsx │ ├── Root │ │ ├── KeystoneCustom.module.scss │ │ └── OnboardCustom.module.scss │ ├── forms │ │ ├── Field │ │ │ └── index.tsx │ │ ├── TextAreaField │ │ │ └── index.tsx │ │ └── GnoForm │ │ │ └── index.tsx │ ├── LoaderContainer │ │ └── index.tsx │ ├── Spacer │ │ └── index.tsx │ ├── FlexSpacer │ │ └── index.tsx │ ├── Table │ │ └── types.d.ts │ ├── SafeListSidebar │ │ └── selectors.ts │ ├── TextBox │ │ └── index.tsx │ ├── List │ │ ├── ListIcon.tsx │ │ └── ListItemText │ │ │ └── index.tsx │ ├── CustomIconText │ │ └── index.tsx │ ├── WhenFieldChanges │ │ └── index.tsx │ ├── PsaBanner │ │ └── index.module.scss │ ├── WalletSwitch │ │ └── index.tsx │ ├── ScanQRModal │ │ └── style.ts │ ├── ChainIndicator │ │ ├── index.tsx │ │ └── index.test.tsx │ ├── InfoAlert │ │ └── index.tsx │ ├── DecodeTxs │ │ └── __tests__ │ │ │ └── index.ts │ └── Divider │ │ └── index.tsx ├── routes │ ├── opening │ │ ├── assets │ │ │ └── creation-process.gif │ │ ├── container │ │ │ ├── index.tsx │ │ │ └── selector.ts │ │ └── utils │ │ │ └── getSafeAddressFromLogs.ts │ ├── safe │ │ └── components │ │ │ ├── Balances │ │ │ ├── assets │ │ │ │ └── nft_icon.png │ │ │ ├── SendModal │ │ │ │ ├── utils.tsx │ │ │ │ └── screens │ │ │ │ │ ├── assets │ │ │ │ │ ├── arrow-down.svg │ │ │ │ │ ├── token.svg │ │ │ │ │ └── code.svg │ │ │ │ │ ├── ReviewCollectible │ │ │ │ │ └── style.ts │ │ │ │ │ ├── ReviewSendFundsTx │ │ │ │ │ └── style.ts │ │ │ │ │ ├── AddressBookInput │ │ │ │ │ └── style.ts │ │ │ │ │ ├── SendFunds │ │ │ │ │ ├── style.ts │ │ │ │ │ └── TokenSelectField │ │ │ │ │ │ └── style.ts │ │ │ │ │ ├── ModalHeader │ │ │ │ │ └── style.ts │ │ │ │ │ └── ContractInteraction │ │ │ │ │ └── ReviewCustomTx │ │ │ │ │ └── style.ts │ │ │ ├── Collectibles │ │ │ │ └── img │ │ │ │ │ ├── marblecards.png │ │ │ │ │ └── cryptokitties.png │ │ │ ├── utils │ │ │ │ ├── index.ts │ │ │ │ ├── setCollectibleImageToPlaceholder.ts │ │ │ │ └── setTokenImgToPlaceholder.ts │ │ │ └── Coins │ │ │ │ └── styles.ts │ │ │ ├── CurrencyDropdown │ │ │ └── img │ │ │ │ └── check.svg │ │ │ ├── Settings │ │ │ ├── SpendingLimit │ │ │ │ ├── FormFields │ │ │ │ │ ├── index.ts │ │ │ │ │ └── Token.tsx │ │ │ │ └── InfoDisplay │ │ │ │ │ ├── index.ts │ │ │ │ │ ├── DataDisplay.tsx │ │ │ │ │ └── ResetTimeInfo.tsx │ │ │ ├── UpdateSafeModal │ │ │ │ └── style.ts │ │ │ ├── ManageOwners │ │ │ │ ├── RemoveOwnerModal │ │ │ │ │ └── screens │ │ │ │ │ │ ├── CheckOwner │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ └── ThresholdForm │ │ │ │ │ │ └── style.ts │ │ │ │ ├── AddOwnerModal │ │ │ │ │ └── screens │ │ │ │ │ │ ├── OwnerForm │ │ │ │ │ │ └── style.ts │ │ │ │ │ │ └── ThresholdForm │ │ │ │ │ │ └── style.ts │ │ │ │ ├── ReplaceOwnerModal │ │ │ │ │ └── screens │ │ │ │ │ │ └── OwnerForm │ │ │ │ │ │ └── style.ts │ │ │ │ └── EditOwnerModal │ │ │ │ │ └── style.ts │ │ │ ├── ThresholdSettings │ │ │ │ ├── ChangeThreshold │ │ │ │ │ └── style.ts │ │ │ │ └── style.ts │ │ │ ├── SafeDetails │ │ │ │ └── style.ts │ │ │ ├── assets │ │ │ │ └── icons │ │ │ │ │ ├── bin.svg │ │ │ │ │ ├── disabled-bin.svg │ │ │ │ │ ├── SafeDetailsIcon.tsx │ │ │ │ │ ├── RequiredConfirmationsIcon.tsx │ │ │ │ │ └── OwnersIcon.tsx │ │ │ ├── RemoveSafeModal │ │ │ │ └── style.ts │ │ │ └── Advanced │ │ │ │ └── style.ts │ │ │ ├── Transactions │ │ │ ├── TxList │ │ │ │ ├── assets │ │ │ │ │ ├── incoming.svg │ │ │ │ │ ├── outgoing.svg │ │ │ │ │ ├── custom.svg │ │ │ │ │ ├── filter-icon.svg │ │ │ │ │ ├── plus-circle-green.svg │ │ │ │ │ ├── check-circle-green.svg │ │ │ │ │ ├── settings.svg │ │ │ │ │ └── circle-cross-red.svg │ │ │ │ ├── modals │ │ │ │ │ └── style.ts │ │ │ │ ├── ThresholdWarning.tsx │ │ │ │ ├── Filter │ │ │ │ │ └── validation.ts │ │ │ │ ├── TxHoverProvider.tsx │ │ │ │ ├── InfoDetails.tsx │ │ │ │ ├── TxModuleInfo.tsx │ │ │ │ ├── TxLocationProvider.tsx │ │ │ │ ├── BatchExecuteHoverProvider.tsx │ │ │ │ ├── hooks │ │ │ │ │ └── useHistoryTransactions.ts │ │ │ │ └── TxInfo.tsx │ │ │ ├── helpers │ │ │ │ ├── useSafeTxGas.ts │ │ │ │ ├── EditTxParametersForm │ │ │ │ │ └── style.ts │ │ │ │ └── Simulation │ │ │ │ │ └── simulation.ts │ │ │ └── __tests__ │ │ │ │ └── utils.test.ts │ │ │ ├── AddressBook │ │ │ ├── EllipsisTransactionDetails │ │ │ │ └── copy.svg │ │ │ ├── CreateEditEntryModal │ │ │ │ └── style.ts │ │ │ └── HelpInfo │ │ │ │ └── index.tsx │ │ │ ├── Apps │ │ │ ├── hooks │ │ │ │ └── permissions │ │ │ │ │ ├── usePermissions.ts │ │ │ │ │ └── index.ts │ │ │ └── components │ │ │ │ ├── SecurityFeedbackModal │ │ │ │ ├── styles.tsx │ │ │ │ └── constants.ts │ │ │ │ ├── PermissionCheckbox.tsx │ │ │ │ └── ThirdPartyCookiesWarning.tsx │ │ │ ├── assets │ │ │ ├── AddressBookIcon.tsx │ │ │ ├── BalancesIcon.tsx │ │ │ └── TransactionsIcon.tsx │ │ │ └── SafeLoadError.tsx │ ├── Home │ │ └── index.tsx │ └── LoadSafePage │ │ └── fields │ │ └── loadFields.tsx ├── utils │ ├── storage │ │ ├── local.ts │ │ ├── session.ts │ │ ├── __tests__ │ │ │ └── index.test.ts │ │ └── useCachedState.ts │ ├── requests.ts │ ├── camelCaseToSpaces.ts │ ├── wrapInSuspense.tsx │ ├── mm_warnings.ts │ ├── events │ │ ├── mobile-app-promotion.ts │ │ ├── utils.ts │ │ ├── wallet.ts │ │ └── assets.ts │ ├── objects.ts │ ├── decodeTx.ts │ ├── getByteLength.ts │ ├── url.ts │ ├── clipboard.ts │ ├── prefixedAddress.ts │ ├── css.ts │ ├── history.ts │ ├── checksumAddress.ts │ ├── __tests__ │ │ └── checksumAddress.test.ts │ └── isValidAddress.ts ├── types │ ├── helpers.d.ts │ └── definitions.d.ts ├── theme │ └── size.ts └── config │ ├── utils.ts │ └── __tests__ │ ├── index.test.ts │ └── utils.test.ts ├── .rescriptsrc ├── public ├── favicon.ico ├── resources │ ├── error.png │ ├── favicon.ico │ ├── background.png │ ├── og-image.png │ ├── logo_120x120.png │ ├── mstile-70x70.png │ ├── favicon-16x16.png │ ├── favicon-32x32.png │ ├── logo-white-bg.png │ ├── mstile-144x144.png │ ├── mstile-150x150.png │ ├── mstile-310x150.png │ ├── mstile-310x310.png │ ├── apple-touch-icon.png │ ├── android-chrome-192x192.png │ ├── android-chrome-512x512.png │ └── site.webmanifest ├── rebranding-banner.png ├── safe-apps-security-practices │ ├── 1.png │ ├── 2.png │ └── 3.png └── migrate.html ├── .husky ├── pre-commit └── pre-push ├── cypress.json ├── .dockerignore ├── .storybook ├── preview-body.html ├── main.js └── preview.js ├── .prettierrc ├── .eslintignore ├── .prettierignore ├── .github ├── PULL_REQUEST_TEMPLATE.md ├── workflows │ └── lint.yml └── ISSUE_TEMPLATE │ ├── bug-report.md │ └── feature-request.md ├── config └── jest │ ├── Web3Mock.js │ ├── fileTransform.js │ └── cssTransform.js ├── docker-compose.yml ├── scripts ├── rescripts │ └── webpack.js ├── update-mocks.sh ├── cypress.js └── github │ ├── prepare_production_deployment.sh │ └── deploy_release.sh ├── Dockerfile ├── .gitignore ├── docker └── nginx.conf ├── prod.Dockerfile ├── tsconfig.json └── patches ├── web3-eth-ens+1.6.0.patch └── web3-eth+1.6.0.patch /.nvmrc: -------------------------------------------------------------------------------- 1 | 14 2 | -------------------------------------------------------------------------------- /cypress/ci.json: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/logic/safe/utils/index.ts: -------------------------------------------------------------------------------- 1 | export * from './safeStorage' 2 | -------------------------------------------------------------------------------- /src/react-app-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | -------------------------------------------------------------------------------- /.rescriptsrc: -------------------------------------------------------------------------------- 1 | module.exports = [require.resolve('./scripts/rescripts/webpack.js')] -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/src/assets/logo.png -------------------------------------------------------------------------------- /public/resources/error.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/public/resources/error.png -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn lint-staged --allow-empty 5 | -------------------------------------------------------------------------------- /.husky/pre-push: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | . "$(dirname "$0")/_/husky.sh" 3 | 4 | yarn tsc --noEmit --incremental 5 | -------------------------------------------------------------------------------- /cypress.json: -------------------------------------------------------------------------------- 1 | { 2 | "baseUrl": "http://localhost:3000/app", 3 | "trashAssetsBeforeRuns" : true 4 | } 5 | -------------------------------------------------------------------------------- /public/rebranding-banner.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/public/rebranding-banner.png -------------------------------------------------------------------------------- /public/resources/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/public/resources/favicon.ico -------------------------------------------------------------------------------- /public/resources/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/public/resources/background.png -------------------------------------------------------------------------------- /public/resources/og-image.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/public/resources/og-image.png -------------------------------------------------------------------------------- /src/components/layout/Pre/index.module.scss: -------------------------------------------------------------------------------- 1 | .pre { 2 | text-overflow: ellipsis; 3 | overflow-x: hidden; 4 | } -------------------------------------------------------------------------------- /.dockerignore: -------------------------------------------------------------------------------- 1 | **/node_modules 2 | **/coverage 3 | **/build 4 | .DS_Store 5 | **/.idea 6 | **/dist 7 | *Dockerfile* 8 | -------------------------------------------------------------------------------- /public/resources/logo_120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/public/resources/logo_120x120.png -------------------------------------------------------------------------------- /public/resources/mstile-70x70.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/public/resources/mstile-70x70.png -------------------------------------------------------------------------------- /src/logic/notifications/index.ts: -------------------------------------------------------------------------------- 1 | export * from './notificationTypes' 2 | export * from './txNotificationBuilder' 3 | -------------------------------------------------------------------------------- /cypress/fixtures/rinkeby_safe_QR.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/cypress/fixtures/rinkeby_safe_QR.png -------------------------------------------------------------------------------- /public/resources/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/public/resources/favicon-16x16.png -------------------------------------------------------------------------------- /public/resources/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/public/resources/favicon-32x32.png -------------------------------------------------------------------------------- /public/resources/logo-white-bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/public/resources/logo-white-bg.png -------------------------------------------------------------------------------- /public/resources/mstile-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/public/resources/mstile-144x144.png -------------------------------------------------------------------------------- /public/resources/mstile-150x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/public/resources/mstile-150x150.png -------------------------------------------------------------------------------- /public/resources/mstile-310x150.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/public/resources/mstile-310x150.png -------------------------------------------------------------------------------- /public/resources/mstile-310x310.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/public/resources/mstile-310x310.png -------------------------------------------------------------------------------- /src/assets/fonts/Averta-normal.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/src/assets/fonts/Averta-normal.woff2 -------------------------------------------------------------------------------- /public/resources/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/public/resources/apple-touch-icon.png -------------------------------------------------------------------------------- /public/safe-apps-security-practices/1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/public/safe-apps-security-practices/1.png -------------------------------------------------------------------------------- /public/safe-apps-security-practices/2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/public/safe-apps-security-practices/2.png -------------------------------------------------------------------------------- /public/safe-apps-security-practices/3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/public/safe-apps-security-practices/3.png -------------------------------------------------------------------------------- /src/assets/fonts/Averta-ExtraBold.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/src/assets/fonts/Averta-ExtraBold.woff2 -------------------------------------------------------------------------------- /.storybook/preview-body.html: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /public/resources/android-chrome-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/public/resources/android-chrome-192x192.png -------------------------------------------------------------------------------- /public/resources/android-chrome-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/public/resources/android-chrome-512x512.png -------------------------------------------------------------------------------- /src/routes/opening/assets/creation-process.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/src/routes/opening/assets/creation-process.gif -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "tabWidth": 2, 3 | "printWidth": 120, 4 | "trailingComma": "all", 5 | "singleQuote": true, 6 | "semi": false 7 | } 8 | -------------------------------------------------------------------------------- /src/components/CookiesBanner/assets/intercom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/src/components/CookiesBanner/assets/intercom.png -------------------------------------------------------------------------------- /src/utils/storage/local.ts: -------------------------------------------------------------------------------- 1 | import Storage from './Storage' 2 | 3 | const local = new Storage(window.localStorage) 4 | 5 | export default local 6 | -------------------------------------------------------------------------------- /src/components/AppLayout/MobileStart/assets/phone@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/src/components/AppLayout/MobileStart/assets/phone@2x.png -------------------------------------------------------------------------------- /src/components/Root/KeystoneCustom.module.scss: -------------------------------------------------------------------------------- 1 | /* Onboard.js custom styles */ 2 | 3 | :global(.ReactModal__Overlay) { 4 | z-index: 2002 !important; 5 | } 6 | -------------------------------------------------------------------------------- /src/logic/wallets/transactionDataCheck/images/ledger.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/src/logic/wallets/transactionDataCheck/images/ledger.jpg -------------------------------------------------------------------------------- /src/routes/safe/components/Balances/assets/nft_icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/src/routes/safe/components/Balances/assets/nft_icon.png -------------------------------------------------------------------------------- /src/components/AppLayout/Header/assets/transition-logo.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/src/components/AppLayout/Header/assets/transition-logo.gif -------------------------------------------------------------------------------- /src/types/helpers.d.ts: -------------------------------------------------------------------------------- 1 | export type Overwrite = Pick> & U 2 | 3 | export type Await = T extends PromiseLike ? U : T 4 | -------------------------------------------------------------------------------- /src/utils/requests.ts: -------------------------------------------------------------------------------- 1 | export enum FETCH_STATUS { 2 | NOT_ASKED = 'NOT_ASKED', 3 | LOADING = 'LOADING', 4 | SUCCESS = 'SUCCESS', 5 | ERROR = 'ERROR', 6 | } 7 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | !.eslintrc.js 2 | build 3 | config 4 | flow-typed 5 | flow-typed/npm 6 | migrations 7 | node_modules 8 | src/assets 9 | test 10 | *.spec* 11 | *.test* -------------------------------------------------------------------------------- /src/logic/safe/transactions/index.ts: -------------------------------------------------------------------------------- 1 | export * from './send' 2 | export * from './offchainSigner' 3 | export * from './txHistory' 4 | export * from './notifiedTransactions' 5 | -------------------------------------------------------------------------------- /src/routes/safe/components/Balances/SendModal/utils.tsx: -------------------------------------------------------------------------------- 1 | export const getStepTitle = (currentStep: number, totalSteps: number): string => `Step ${currentStep} of ${totalSteps}` 2 | -------------------------------------------------------------------------------- /src/routes/safe/components/Balances/Collectibles/img/marblecards.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/src/routes/safe/components/Balances/Collectibles/img/marblecards.png -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | !.eslintrc.js 2 | build 3 | /config 4 | /contracts 5 | flow-typed 6 | flow-typed/npm 7 | migrations 8 | node_modules 9 | src/assets 10 | src/types/contracts 11 | test -------------------------------------------------------------------------------- /src/routes/safe/components/Balances/Collectibles/img/cryptokitties.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/src/routes/safe/components/Balances/Collectibles/img/cryptokitties.png -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## What it solves 2 | Resolves # 3 | 4 | ## How this PR fixes it 5 | 6 | ## How to test it 7 | 8 | ## Analytics changes 9 | 10 | ## Screenshots 11 | -------------------------------------------------------------------------------- /src/components/forms/Field/index.tsx: -------------------------------------------------------------------------------- 1 | import { Field } from 'react-final-form' 2 | 3 | const GnoField = (props: any): React.ReactElement => 4 | 5 | export default GnoField 6 | -------------------------------------------------------------------------------- /src/components/AppLayout/Header/components/WalletIcon/icons/icon-opera.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/src/components/AppLayout/Header/components/WalletIcon/icons/icon-opera.png -------------------------------------------------------------------------------- /src/logic/config/store/reducer/reducer.d.ts: -------------------------------------------------------------------------------- 1 | import { ChainId } from 'src/config/chain.d' 2 | 3 | export type ConfigState = { 4 | chainId: ChainId 5 | } 6 | 7 | export type ConfigPayload = ChainId 8 | -------------------------------------------------------------------------------- /src/components/AppLayout/Header/components/WalletIcon/icons/icon-authereum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/src/components/AppLayout/Header/components/WalletIcon/icons/icon-authereum.png -------------------------------------------------------------------------------- /src/components/AppLayout/Header/components/WalletIcon/icons/icon-keystone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/src/components/AppLayout/Header/components/WalletIcon/icons/icon-keystone.png -------------------------------------------------------------------------------- /src/components/AppLayout/Header/components/WalletIcon/icons/icon-metamask.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/src/components/AppLayout/Header/components/WalletIcon/icons/icon-metamask.png -------------------------------------------------------------------------------- /src/components/AppLayout/Header/components/WalletIcon/icons/icon-opera-touch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/src/components/AppLayout/Header/components/WalletIcon/icons/icon-opera-touch.png -------------------------------------------------------------------------------- /src/components/AppLayout/Header/components/WalletIcon/icons/icon-squarelink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/5afe/safe-react/HEAD/src/components/AppLayout/Header/components/WalletIcon/icons/icon-squarelink.png -------------------------------------------------------------------------------- /src/routes/opening/container/index.tsx: -------------------------------------------------------------------------------- 1 | import { connect } from 'react-redux' 2 | 3 | import selector from './selector' 4 | 5 | import Opening from './index' 6 | 7 | export default connect(selector)(Opening) 8 | -------------------------------------------------------------------------------- /src/routes/safe/components/Balances/utils/index.ts: -------------------------------------------------------------------------------- 1 | export { setImageToPlaceholder } from './setTokenImgToPlaceholder' 2 | export { setCollectibleImageToPlaceholder } from './setCollectibleImageToPlaceholder' 3 | -------------------------------------------------------------------------------- /src/utils/camelCaseToSpaces.ts: -------------------------------------------------------------------------------- 1 | export const camelCaseToSpaces = (str: string): string => { 2 | return str 3 | .replace(/([A-Z][a-z0-9]+)/g, ' $1 ') 4 | .replace(/\s{2}/g, ' ') 5 | .trim() 6 | } 7 | -------------------------------------------------------------------------------- /src/logic/safe/store/actions/clearSafeList.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | 3 | export const CLEAR_SAFE_LIST = 'CLEAR_SAFE_LIST' 4 | 5 | export const clearSafeList = createAction(CLEAR_SAFE_LIST) 6 | -------------------------------------------------------------------------------- /.storybook/main.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | stories: ['../src/**/*.stories.tsx'], 3 | addons: [ 4 | '@storybook/preset-create-react-app', 5 | '@storybook/addon-actions', 6 | '@storybook/addon-links', 7 | ], 8 | }; 9 | -------------------------------------------------------------------------------- /src/logic/safe/store/actions/removeSafe.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | 3 | export const REMOVE_SAFE = 'REMOVE_SAFE' 4 | 5 | const removeSafe = createAction(REMOVE_SAFE) 6 | 7 | export default removeSafe 8 | -------------------------------------------------------------------------------- /src/logic/tokens/store/actions/addTokens.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | 3 | export const ADD_TOKENS = 'ADD_TOKENS' 4 | 5 | export const addTokens = createAction(ADD_TOKENS, (tokens) => ({ 6 | tokens, 7 | })) 8 | -------------------------------------------------------------------------------- /config/jest/Web3Mock.js: -------------------------------------------------------------------------------- 1 | import Web3 from 'web3' 2 | 3 | const window = global.window || {} 4 | window.web3 = {} 5 | window.web3.currentProvider = new Web3.providers.HttpProvider('http://localhost:8545') 6 | 7 | global.window = window 8 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.2' 2 | 3 | services: 4 | safe-react: 5 | build: ./ 6 | volumes: 7 | - /app/node_modules 8 | - /app/src/types/contracts 9 | - ./:/app 10 | ports: 11 | - 3000:3000 12 | -------------------------------------------------------------------------------- /src/routes/safe/components/CurrencyDropdown/img/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: 'ESLint check' 2 | on: [pull_request] 3 | 4 | jobs: 5 | eslint: 6 | runs-on: ubuntu-latest 7 | steps: 8 | - uses: actions/checkout@v2 9 | - uses: gnosis/safe-react-eslint-plus-action@v3.5.0 10 | -------------------------------------------------------------------------------- /scripts/rescripts/webpack.js: -------------------------------------------------------------------------------- 1 | const { removeWebpackPlugin } = require('@rescripts/utilities') 2 | 3 | module.exports = (config) => { 4 | const webpackWithoutEsLint = removeWebpackPlugin('ESLintWebpackPlugin', config) 5 | return webpackWithoutEsLint 6 | } 7 | -------------------------------------------------------------------------------- /src/components/Root/OnboardCustom.module.scss: -------------------------------------------------------------------------------- 1 | /* Onboard.js custom styles */ 2 | 3 | :global(.bn-onboard-custom.bn-onboard-modal) { 4 | font-family: 'Averta'; 5 | z-index: 2001; 6 | } 7 | 8 | :global(.torusIframe) { 9 | z-index: 9999; 10 | } 11 | -------------------------------------------------------------------------------- /src/logic/currentSession/store/actions/clearCurrentSession.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | 3 | export const CLEAR_CURRENT_SESSION = 'CLEAR_CURRENT_SESSION' 4 | 5 | export const clearCurrentSession = createAction(CLEAR_CURRENT_SESSION) 6 | -------------------------------------------------------------------------------- /src/logic/safe/utils/mocks/getChainsConfigMock.ts: -------------------------------------------------------------------------------- 1 | import { ChainListResponse } from '@gnosis.pm/safe-react-gateway-sdk' 2 | import mockData from './remoteConfig.json' 3 | 4 | export const mockGetChainsConfigResponse = mockData as unknown as ChainListResponse 5 | -------------------------------------------------------------------------------- /src/routes/opening/container/selector.ts: -------------------------------------------------------------------------------- 1 | import { createStructuredSelector } from 'reselect' 2 | 3 | import { networkSelector } from 'src/logic/wallets/store/selectors' 4 | 5 | export default createStructuredSelector({ 6 | network: networkSelector, 7 | }) 8 | -------------------------------------------------------------------------------- /src/components/LoaderContainer/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | export const LoadingContainer = styled.div` 4 | width: 100%; 5 | height: 100%; 6 | display: flex; 7 | align-items: center; 8 | justify-content: center; 9 | ` 10 | -------------------------------------------------------------------------------- /src/components/layout/Typography/index.ts: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import Paragraph from 'src/components/layout/Paragraph' 3 | 4 | export const Overline = styled(Paragraph)` 5 | font-size: 11px; 6 | line-height: 14px; 7 | letter-spacing: 1px; 8 | ` 9 | -------------------------------------------------------------------------------- /src/routes/safe/components/Settings/SpendingLimit/FormFields/index.ts: -------------------------------------------------------------------------------- 1 | import Amount from './Amount' 2 | import Beneficiary from './Beneficiary' 3 | import ResetTime from './ResetTime' 4 | import Token from './Token' 5 | 6 | export { Amount, Beneficiary, ResetTime, Token } 7 | -------------------------------------------------------------------------------- /src/routes/safe/components/Balances/SendModal/screens/assets/arrow-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/routes/safe/components/Transactions/TxList/assets/incoming.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/routes/safe/components/Transactions/TxList/assets/outgoing.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Spacer/index.tsx: -------------------------------------------------------------------------------- 1 | import { ReactElement } from 'react' 2 | 3 | const style = { 4 | flexGrow: 1, 5 | } 6 | 7 | const Spacer = ({ className }: { className?: string }): ReactElement =>
8 | 9 | export default Spacer 10 | -------------------------------------------------------------------------------- /src/logic/currentSession/store/actions/removeViewedSafe.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | 3 | export const REMOVE_VIEWED_SAFE = 'REMOVE_VIEWED_SAFE' 4 | 5 | const removeViewedSafe = createAction(REMOVE_VIEWED_SAFE) 6 | 7 | export default removeViewedSafe 8 | -------------------------------------------------------------------------------- /src/logic/currentSession/store/actions/updateViewedSafes.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | 3 | export const UPDATE_VIEWED_SAFES = 'UPDATE_VIEWED_SAFES' 4 | 5 | const updateViewedSafes = createAction(UPDATE_VIEWED_SAFES) 6 | 7 | export default updateViewedSafes 8 | -------------------------------------------------------------------------------- /src/components/AppLayout/Header/assets/dotRinkeby.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/logic/cookies/store/selectors/index.ts: -------------------------------------------------------------------------------- 1 | import { CookieState, COOKIES_REDUCER_ID } from 'src/logic/cookies/store/reducer/cookies' 2 | import { AppReduxState } from 'src/store' 3 | 4 | export const cookieBannerState = (state: AppReduxState): CookieState => state[COOKIES_REDUCER_ID] 5 | -------------------------------------------------------------------------------- /src/logic/currentSession/store/actions/loadCurrentSession.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | 3 | export const LOAD_CURRENT_SESSION = 'LOAD_CURRENT_SESSION' 4 | 5 | const loadCurrentSession = createAction(LOAD_CURRENT_SESSION) 6 | 7 | export default loadCurrentSession 8 | -------------------------------------------------------------------------------- /src/logic/safe/store/actions/setLastOpenedSafe.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | 3 | export const SET_LAST_OPENED_SAFE = 'SET_LAST_OPENED_SAFE' 4 | 5 | export const setLastOpenedSafe = createAction(SET_LAST_OPENED_SAFE, (address: string) => ({ 6 | address, 7 | })) 8 | -------------------------------------------------------------------------------- /src/logic/wallets/store/actions/index.ts: -------------------------------------------------------------------------------- 1 | export enum PROVIDER_ACTIONS { 2 | WALLET_NAME = 'provider/nameUpdated', 3 | ACCOUNT = 'provider/accountUpdated', 4 | SMART_CONTRACT = 'provider/smartContract', 5 | NETWORK = 'provider/networkUpdated', 6 | ENS = 'provider/ensUpdated', 7 | } 8 | -------------------------------------------------------------------------------- /src/routes/safe/components/Settings/SpendingLimit/InfoDisplay/index.ts: -------------------------------------------------------------------------------- 1 | import AddressInfo from './AddressInfo' 2 | import ResetTimeInfo from './ResetTimeInfo' 3 | import TokenInfo from './TokenInfo' 4 | 5 | export { AddressInfo, ResetTimeInfo, TokenInfo } 6 | export default './DataDisplay' 7 | -------------------------------------------------------------------------------- /src/components/AppLayout/Header/assets/triangle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/logic/safe/store/models/types/confirmation.ts: -------------------------------------------------------------------------------- 1 | import { RecordOf } from 'immutable' 2 | 3 | export type ConfirmationProps = { 4 | owner: string 5 | type: string 6 | hash: string 7 | signature: string | null 8 | } 9 | 10 | export type Confirmation = RecordOf 11 | -------------------------------------------------------------------------------- /src/logic/currentSession/store/actions/addCurrentShortName.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | 3 | export const ADD_CURRENT_SHORT_NAME = 'ADD_CURRENT_SHORT_NAME' 4 | 5 | const addCurrentShortName = createAction(ADD_CURRENT_SHORT_NAME) 6 | 7 | export default addCurrentShortName 8 | -------------------------------------------------------------------------------- /src/logic/safe/store/actions/updateSafe.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | 3 | import { SafeRecordProps } from 'src/logic/safe/store/models/safe' 4 | 5 | export const UPDATE_SAFE = 'UPDATE_SAFE' 6 | 7 | export const updateSafe = createAction>(UPDATE_SAFE) 8 | -------------------------------------------------------------------------------- /src/utils/wrapInSuspense.tsx: -------------------------------------------------------------------------------- 1 | import { Suspense, SuspenseProps } from 'react' 2 | 3 | export const wrapInSuspense = ( 4 | component: Required, 5 | fallback: SuspenseProps['fallback'] = null, 6 | ): React.ReactElement => {component} 7 | -------------------------------------------------------------------------------- /scripts/update-mocks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | echo 'Updating Jest mocks...' 4 | 5 | # Fetch config 6 | config=$(curl -s 'https://safe-client.staging.5afe.dev/v1/chains/') 7 | 8 | # Pretty-print the JSON 9 | node -p "JSON.stringify($config, null, 2)" > src/logic/safe/utils/mocks/remoteConfig.json 10 | -------------------------------------------------------------------------------- /src/components/layout/Img/index.module.scss: -------------------------------------------------------------------------------- 1 | .img { 2 | max-width: 100%; 3 | box-sizing: border-box; 4 | } 5 | 6 | .bordered { 7 | border: 1px solid #ddd; 8 | } 9 | 10 | .fullwidth { 11 | padding: 0; 12 | width: 40% !important; 13 | margin: 0 60% 25px !important; 14 | } 15 | -------------------------------------------------------------------------------- /src/logic/safe/store/models/confirmation.ts: -------------------------------------------------------------------------------- 1 | import { Record } from 'immutable' 2 | import { ConfirmationProps } from './types/confirmation' 3 | 4 | export const makeConfirmation = Record({ 5 | owner: '', 6 | type: 'initialised', 7 | hash: '', 8 | signature: null, 9 | }) 10 | -------------------------------------------------------------------------------- /cypress/fixtures/address_book_test.csv: -------------------------------------------------------------------------------- 1 | address,name,chainId 2 | 0x730F87dA2A3C6721e2196DFB990759e9bdfc5083,rinkeby user 1, 4 3 | 0xFD71c1ABadBD37F60E4C8F208386dDFC4d2Bf01f,rinkeby user 2, 4 4 | 0x61a0c717d18232711bC788F19C9Cd56a43cc8872,gno user 1, 100 5 | 0x66bE167c36B3b75D1130BBbDec69f9f04E7DA4fC,gno user 2, 100 -------------------------------------------------------------------------------- /src/components/layout/Bold/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | class Bold extends React.PureComponent { 4 | render(): React.ReactElement { 5 | const { children, ...props } = this.props 6 | 7 | return {children} 8 | } 9 | } 10 | 11 | export default Bold 12 | -------------------------------------------------------------------------------- /src/utils/mm_warnings.ts: -------------------------------------------------------------------------------- 1 | // https://docs.metamask.io/guide/ethereum-provider.html#ethereum-autorefreshonnetworkchange 2 | export const disableMMAutoRefreshWarning = (): void => { 3 | if (window.ethereum && window.ethereum.isMetaMask) { 4 | window.ethereum.autoRefreshOnNetworkChange = false 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /src/components/layout/Page/index.module.scss: -------------------------------------------------------------------------------- 1 | @import "src/theme/variables.scss"; 2 | 3 | .page { 4 | display: flex; 5 | flex: 1 0 auto; 6 | flex-direction: column; 7 | padding: 16px 0 0 0; 8 | } 9 | 10 | .center { 11 | align-self: center; 12 | } 13 | 14 | .overflow { 15 | overflow-x: scroll; 16 | } -------------------------------------------------------------------------------- /src/components/layout/Span/index.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | 3 | class Span extends React.PureComponent { 4 | render(): React.ReactElement { 5 | const { children, ...props } = this.props 6 | 7 | return {children} 8 | } 9 | } 10 | 11 | export default Span 12 | -------------------------------------------------------------------------------- /src/logic/currentSession/store/actions/addCurrentSafeAddress.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | 3 | export const ADD_CURRENT_SAFE_ADDRESS = 'ADD_CURRENT_SAFE_ADDRESS' 4 | 5 | const addCurrentSafeAddress = createAction(ADD_CURRENT_SAFE_ADDRESS) 6 | 7 | export default addCurrentSafeAddress 8 | -------------------------------------------------------------------------------- /src/logic/hooks/useQuery.ts: -------------------------------------------------------------------------------- 1 | import { useMemo } from 'react' 2 | import { useLocation } from 'react-router-dom' 3 | 4 | const useQuery = (): URLSearchParams => { 5 | const { search } = useLocation() 6 | 7 | return useMemo(() => new URLSearchParams(search), [search]) 8 | } 9 | 10 | export { useQuery } 11 | -------------------------------------------------------------------------------- /src/logic/safe/store/actions/types.d.ts: -------------------------------------------------------------------------------- 1 | import { ThunkDispatch } from 'redux-thunk' 2 | import { AnyAction } from 'redux' 3 | 4 | import { AppReduxState } from 'src/store' 5 | 6 | export type DispatchReturn = string | undefined 7 | 8 | export type Dispatch = ThunkDispatch 9 | -------------------------------------------------------------------------------- /src/logic/safe/store/actions/addOrUpdateSafe.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | 3 | import { SafeRecordProps } from 'src/logic/safe/store/models/safe' 4 | 5 | export const ADD_OR_UPDATE_SAFE = 'ADD_OR_UPDATE_SAFE' 6 | 7 | export const addOrUpdateSafe = createAction(ADD_OR_UPDATE_SAFE) 8 | -------------------------------------------------------------------------------- /src/utils/storage/session.ts: -------------------------------------------------------------------------------- 1 | import Storage from './Storage' 2 | 3 | const session = new Storage(window.sessionStorage) 4 | 5 | export default session 6 | 7 | export const { 8 | getItem: loadFromSessionStorage, 9 | setItem: saveToSessionStorage, 10 | removeItem: removeFromSessionStorage, 11 | } = session 12 | -------------------------------------------------------------------------------- /src/logic/appearance/actions/setCopyShortName.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | 3 | import { SetCopyShortNamePayload } from '../reducer/appearance' 4 | 5 | export const SET_COPY_SHORT_NAME = 'SET_COPY_SHORT_NAME' 6 | 7 | export const setCopyShortName = createAction(SET_COPY_SHORT_NAME) 8 | -------------------------------------------------------------------------------- /src/logic/appearance/actions/setShowShortName.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | 3 | import { SetShowShortNamePayload } from '../reducer/appearance' 4 | 5 | export const SET_SHOW_SHORT_NAME = 'SET_SHOW_SHORT_NAME' 6 | 7 | export const setShowShortName = createAction(SET_SHOW_SHORT_NAME) 8 | -------------------------------------------------------------------------------- /src/routes/safe/components/Settings/UpdateSafeModal/style.ts: -------------------------------------------------------------------------------- 1 | import { lg, md } from 'src/theme/variables' 2 | import { createStyles, makeStyles } from '@material-ui/core' 3 | 4 | export const useStyles = makeStyles( 5 | createStyles({ 6 | modalContent: { 7 | padding: `${md} ${lg}`, 8 | }, 9 | }), 10 | ) 11 | -------------------------------------------------------------------------------- /src/components/FlexSpacer/index.tsx: -------------------------------------------------------------------------------- 1 | // This component is used to create an empty div element to use inside a flex container. 2 | // It can be added into a flex component and use to justify content leaving space around with easier alignment rules. 3 | const FlexSpacer = (): React.ReactElement =>
4 | 5 | export default FlexSpacer 6 | -------------------------------------------------------------------------------- /src/routes/safe/components/Transactions/TxList/assets/custom.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/utils/events/mobile-app-promotion.ts: -------------------------------------------------------------------------------- 1 | import { GTM_EVENT } from '../googleTagManager' 2 | 3 | const MOBILE_APP_EVENTS = { 4 | appstoreButtonClick: { 5 | event: GTM_EVENT.CLICK, 6 | category: 'mobile-app-promotion', 7 | action: 'appstore-button-click', 8 | }, 9 | } 10 | 11 | export default MOBILE_APP_EVENTS 12 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM node:14 2 | 3 | RUN apt-get update \ 4 | && apt-get install -y libusb-1.0-0 libusb-1.0-0-dev libudev-dev \ 5 | && rm -rf /var/lib/apt/lists/* 6 | 7 | WORKDIR /app 8 | 9 | COPY package.json yarn.lock . 10 | 11 | RUN yarn install 12 | 13 | COPY . . 14 | 15 | EXPOSE 3000 16 | 17 | CMD ["yarn", "start"] 18 | -------------------------------------------------------------------------------- /src/assets/icons/shape.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/AppLayout/Header/assets/key.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/logic/safe/api/fetchSafeApps.ts: -------------------------------------------------------------------------------- 1 | import { getSafeApps, SafeAppData } from '@gnosis.pm/safe-react-gateway-sdk' 2 | 3 | import { _getChainId } from 'src/config' 4 | 5 | export const fetchSafeAppsList = async (): Promise => { 6 | return getSafeApps(_getChainId(), { 7 | client_url: window.location.origin, 8 | }) 9 | } 10 | -------------------------------------------------------------------------------- /src/logic/safe/store/actions/setLatestMasterContractVersion.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | 3 | export const SET_LATEST_MASTER_CONTRACT_VERSION = 'SET_LATEST_MASTER_CONTRACT_VERSION' 4 | 5 | const setLatestMasterContractVersion = createAction(SET_LATEST_MASTER_CONTRACT_VERSION) 6 | 7 | export default setLatestMasterContractVersion 8 | -------------------------------------------------------------------------------- /config/jest/fileTransform.js: -------------------------------------------------------------------------------- 1 | const path = require('path') 2 | 3 | // This is a custom Jest transformer turning file imports into filenames. 4 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 5 | 6 | module.exports = { 7 | process(src, filename) { 8 | return `module.exports = ${JSON.stringify(path.basename(filename))};` 9 | }, 10 | } 11 | -------------------------------------------------------------------------------- /src/components/AppLayout/Header/assets/wallet.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/logic/config/store/actions/index.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | 3 | import { _getChainId } from 'src/config' 4 | import { ChainId } from 'src/config/chain.d' 5 | 6 | export enum CONFIG_ACTIONS { 7 | SET_CHAIN_ID = 'config/setChainId', 8 | } 9 | 10 | export const setChainIdAction = createAction(CONFIG_ACTIONS.SET_CHAIN_ID) 11 | -------------------------------------------------------------------------------- /src/logic/currencyValues/store/actions/setSelectedCurrency.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | import { SelectedCurrencyPayload } from 'src/logic/currencyValues/store/reducer/currencyValues' 3 | 4 | export const SET_CURRENT_CURRENCY = 'SET_CURRENT_CURRENCY' 5 | 6 | export const setSelectedCurrency = createAction(SET_CURRENT_CURRENCY) 7 | -------------------------------------------------------------------------------- /src/logic/wallets/store/actions/updateProviderEns.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | 3 | import { PROVIDER_ACTIONS } from 'src/logic/wallets/store/actions' 4 | import { ProviderEnsPayload } from 'src/logic/wallets/store/reducer' 5 | 6 | const updateProviderEns = createAction(PROVIDER_ACTIONS.ENS) 7 | 8 | export default updateProviderEns 9 | -------------------------------------------------------------------------------- /src/logic/collectibles/sources/index.ts: -------------------------------------------------------------------------------- 1 | import Gnosis from 'src/logic/collectibles/sources/Gnosis' 2 | import { COLLECTIBLES_SOURCE } from 'src/utils/constants' 3 | 4 | const SOURCES = { 5 | gnosis: new Gnosis(), 6 | } 7 | 8 | type Sources = typeof SOURCES 9 | 10 | export const getConfiguredSource = (): Sources['gnosis'] => SOURCES[COLLECTIBLES_SOURCE.toLowerCase()] 11 | -------------------------------------------------------------------------------- /cypress/support/index.js: -------------------------------------------------------------------------------- 1 | import '@testing-library/cypress/add-commands' 2 | 3 | Cypress.Commands.add('connectE2EWallet', () => { 4 | cy.on('window:before:load', (window) => { 5 | window.localStorage.setItem( 6 | 'SAFE__lastUsedProvider', 7 | JSON.stringify({ value: 'E2E Wallet', expiry: new Date().getTime() + 3600 * 1000 * 24 }), 8 | ) 9 | }) 10 | }) 11 | -------------------------------------------------------------------------------- /src/components/Table/types.d.ts: -------------------------------------------------------------------------------- 1 | export interface TableColumn { 2 | align?: 'inherit' | 'left' | 'center' | 'right' | 'justify' 3 | custom: boolean 4 | disablePadding: boolean 5 | id: string 6 | label: string 7 | order: boolean 8 | static?: boolean 9 | style?: any 10 | formatTypeSort?: (value: string | number) => string | number 11 | width?: number 12 | } 13 | -------------------------------------------------------------------------------- /src/logic/config/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { ChainId } from 'src/config/chain.d' 2 | import { store } from 'src/store' 3 | import { setChainIdAction } from 'src/logic/config/store/actions' 4 | import { _setChainId } from 'src/config' 5 | 6 | export const setChainId = (newChainId: ChainId) => { 7 | _setChainId(newChainId) 8 | store.dispatch(setChainIdAction(newChainId)) 9 | } 10 | -------------------------------------------------------------------------------- /src/assets/icons/check_bg.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/utils/objects.ts: -------------------------------------------------------------------------------- 1 | export const sortObject = (obj: Record, order = 'asc'): any => { 2 | return Object.keys(obj) 3 | .sort((a: string, b: string) => Number(order === 'desc' ? b : a) - Number(order === 'desc' ? a : b)) 4 | .reduce( 5 | (acc, key) => ({ 6 | ...acc, 7 | [key]: obj[key], 8 | }), 9 | {}, 10 | ) 11 | } 12 | -------------------------------------------------------------------------------- /src/components/layout/Divider/index.tsx: -------------------------------------------------------------------------------- 1 | import { ReactElement } from 'react' 2 | 3 | import { border } from 'src/theme/variables' 4 | 5 | const style = { 6 | borderRight: `solid 2px ${border}`, 7 | height: '100%', 8 | } 9 | 10 | const Divider = ({ className }: { className?: string }): ReactElement =>
11 | 12 | export default Divider 13 | -------------------------------------------------------------------------------- /src/logic/wallets/store/actions/updateProviderWallet.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | 3 | import { PROVIDER_ACTIONS } from 'src/logic/wallets/store/actions' 4 | import { ProviderNamePayload } from 'src/logic/wallets/store/reducer' 5 | 6 | const updateProviderWallet = createAction(PROVIDER_ACTIONS.WALLET_NAME) 7 | 8 | export default updateProviderWallet 9 | -------------------------------------------------------------------------------- /config/jest/cssTransform.js: -------------------------------------------------------------------------------- 1 | // This is a custom Jest transformer turning style imports into empty objects. 2 | // http://facebook.github.io/jest/docs/tutorial-webpack.html 3 | 4 | module.exports = { 5 | process() { 6 | return 'module.exports = {};' 7 | }, 8 | getCacheKey(fileData, filename) { 9 | // The output is always the same. 10 | return 'cssTransform' 11 | }, 12 | } 13 | -------------------------------------------------------------------------------- /src/logic/currencyValues/store/actions/setAvailableCurrencies.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | import { AvailableCurrenciesPayload } from 'src/logic/currencyValues/store/reducer/currencyValues' 3 | 4 | export const SET_AVAILABLE_CURRENCIES = 'SET_AVAILABLE_CURRENCIES' 5 | 6 | export const setAvailableCurrencies = createAction(SET_AVAILABLE_CURRENCIES) 7 | -------------------------------------------------------------------------------- /src/logic/wallets/store/actions/updateProviderAccount.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | 3 | import { PROVIDER_ACTIONS } from 'src/logic/wallets/store/actions' 4 | import { ProviderAccountPayload } from 'src/logic/wallets/store/reducer' 5 | 6 | const updateProviderAccount = createAction(PROVIDER_ACTIONS.ACCOUNT) 7 | 8 | export default updateProviderAccount 9 | -------------------------------------------------------------------------------- /src/logic/wallets/store/actions/updateProviderNetwork.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | 3 | import { PROVIDER_ACTIONS } from 'src/logic/wallets/store/actions' 4 | import { ProviderNetworkPayload } from 'src/logic/wallets/store/reducer' 5 | 6 | const updateProviderNetwork = createAction(PROVIDER_ACTIONS.NETWORK) 7 | 8 | export default updateProviderNetwork 9 | -------------------------------------------------------------------------------- /src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/CheckOwner/style.ts: -------------------------------------------------------------------------------- 1 | import { createStyles, makeStyles } from '@material-ui/core' 2 | 3 | import { lg, md } from 'src/theme/variables' 4 | 5 | export const useStyles = makeStyles( 6 | createStyles({ 7 | formContainer: { 8 | padding: `${md} ${lg}`, 9 | minHeight: '340px', 10 | }, 11 | }), 12 | ) 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /build 3 | .DS_Store 4 | yarn-error.log 5 | .env* 6 | .eslintcache 7 | /.idea 8 | dist 9 | electron-builder.yml 10 | /.yalc 11 | yalc.lock 12 | # testing 13 | /coverage/ 14 | src/types/contracts/ 15 | src/types/gateway/ 16 | tsconfig.tsbuildinfo 17 | public/**/*.js 18 | jest.results.json 19 | *.#* 20 | cypress/videos 21 | cypress/screenshots 22 | cypress/downloads 23 | -------------------------------------------------------------------------------- /src/components/layout/Link/index.module.scss: -------------------------------------------------------------------------------- 1 | @import "src/theme/variables.scss"; 2 | 3 | .link { 4 | text-decoration: none; 5 | } 6 | 7 | .regular { 8 | color: $secondary; 9 | } 10 | 11 | .white { 12 | color: white; 13 | } 14 | 15 | .paddingXs { 16 | padding-right: $xs; 17 | } 18 | 19 | .paddingSm { 20 | padding-right: $sm; 21 | } 22 | 23 | .paddingMd { 24 | padding-right: $md; 25 | } -------------------------------------------------------------------------------- /src/components/layout/Pre/index.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames/bind' 2 | import { ReactElement } from 'react' 3 | 4 | import styles from './index.module.scss' 5 | 6 | const cx = classNames.bind(styles) 7 | 8 | const Pre = ({ children, ...props }): ReactElement => ( 9 |
10 |     {children}
11 |   
12 | ) 13 | 14 | export default Pre 15 | -------------------------------------------------------------------------------- /src/routes/safe/components/Balances/utils/setCollectibleImageToPlaceholder.ts: -------------------------------------------------------------------------------- 1 | import { SyntheticEvent } from 'react' 2 | 3 | import NFTIcon from 'src/routes/safe/components/Balances/assets/nft_icon.png' 4 | 5 | export const setCollectibleImageToPlaceholder = (error: SyntheticEvent): void => { 6 | error.currentTarget.onerror = null 7 | error.currentTarget.src = NFTIcon 8 | } 9 | -------------------------------------------------------------------------------- /docker/nginx.conf: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | 4 | location / { 5 | root /usr/share/nginx/html; 6 | try_files $uri /index.html; 7 | index index.html index.htm; 8 | } 9 | 10 | location ^~ /app { 11 | alias /usr/share/nginx/html; 12 | try_files $uri /index.html; 13 | index index.html index.htm; 14 | } 15 | 16 | 17 | include /etc/nginx/extra-conf.d/*.conf; 18 | } 19 | -------------------------------------------------------------------------------- /src/components/layout/Page/index.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames/bind' 2 | 3 | import styles from './index.module.scss' 4 | 5 | const cx = classNames.bind(styles) 6 | 7 | const Page = ({ align, children, overflow }: any) => ( 8 |
9 | {children} 10 |
11 | ) 12 | 13 | export default Page 14 | -------------------------------------------------------------------------------- /public/resources/site.webmanifest: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Safe", 3 | "short_name": "Safe", 4 | "icons": [ 5 | { 6 | "src": "/android-chrome-192x192.png", 7 | "sizes": "192x192", 8 | "type": "image/png" 9 | }, 10 | { 11 | "src": "/android-chrome-512x512.png", 12 | "sizes": "512x512", 13 | "type": "image/png" 14 | } 15 | ], 16 | "display": "standalone" 17 | } 18 | -------------------------------------------------------------------------------- /src/components/layout/Span/Span.test.jsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from 'src/utils/test-utils' 2 | import Span from './index' 3 | 4 | describe('', () => { 5 | it('Should render Span and his content', () => { 6 | render({'Test Text placeholder'}) 7 | 8 | const spanNode = screen.getByText('Test Text placeholder') 9 | 10 | expect(spanNode).toBeInTheDocument() 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /src/logic/currentSession/store/actions/addViewedSafe.ts: -------------------------------------------------------------------------------- 1 | import { Dispatch } from 'src/logic/safe/store/actions/types.d' 2 | 3 | import updateViewedSafes from 'src/logic/currentSession/store/actions/updateViewedSafes' 4 | 5 | const addViewedSafe = 6 | (safeAddress: string) => 7 | (dispatch: Dispatch): void => { 8 | dispatch(updateViewedSafes(safeAddress)) 9 | } 10 | 11 | export default addViewedSafe 12 | -------------------------------------------------------------------------------- /src/logic/safe/api/fetchSafesByOwner.ts: -------------------------------------------------------------------------------- 1 | import { getOwnedSafes } from '@gnosis.pm/safe-react-gateway-sdk' 2 | 3 | import { _getChainId } from 'src/config' 4 | import { checksumAddress } from 'src/utils/checksumAddress' 5 | 6 | export const fetchSafesByOwner = async (ownerAddress: string): Promise => { 7 | return getOwnedSafes(_getChainId(), checksumAddress(ownerAddress)).then(({ safes }) => safes) 8 | } 9 | -------------------------------------------------------------------------------- /src/routes/safe/components/Transactions/TxList/modals/style.ts: -------------------------------------------------------------------------------- 1 | import { createStyles, makeStyles } from '@material-ui/core' 2 | import { lg, md, sm } from 'src/theme/variables' 3 | 4 | export const useStyles = makeStyles( 5 | createStyles({ 6 | container: { 7 | padding: `${md} ${lg}`, 8 | }, 9 | nonceNumber: { 10 | marginTop: sm, 11 | fontSize: md, 12 | }, 13 | }), 14 | ) 15 | -------------------------------------------------------------------------------- /src/theme/size.ts: -------------------------------------------------------------------------------- 1 | import { lg, md, sm, xl, xs } from 'src/theme/variables' 2 | 3 | export const getSize = (size: string): string => { 4 | switch (size) { 5 | case 'xs': 6 | return xs 7 | case 'sm': 8 | return sm 9 | case 'md': 10 | return md 11 | case 'lg': 12 | return lg 13 | case 'xl': 14 | return xl 15 | default: 16 | return '0px' 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/routes/Home/index.tsx: -------------------------------------------------------------------------------- 1 | import { ReactElement } from 'react' 2 | import Page from 'src/components/layout/Page' 3 | import { Box } from '@material-ui/core' 4 | import Dashboard from 'src/components/Dashboard' 5 | 6 | const Home = (): ReactElement => { 7 | return ( 8 | 9 | 10 | 11 | 12 | 13 | ) 14 | } 15 | 16 | export default Home 17 | -------------------------------------------------------------------------------- /src/components/SafeListSidebar/selectors.ts: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect' 2 | 3 | import { safesWithNamesAsList } from 'src/logic/safe/store/selectors' 4 | 5 | /** 6 | * Sort safe list by the name in the address book 7 | */ 8 | export const sortedSafeListSelector = createSelector([safesWithNamesAsList], (safes) => 9 | safes.sort((a, b) => (a.name.toLowerCase() > b.name.toLowerCase() ? 1 : -1)), 10 | ) 11 | -------------------------------------------------------------------------------- /src/routes/safe/components/Balances/SendModal/screens/assets/token.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/logic/safe/transactions/notifiedTransactions.ts: -------------------------------------------------------------------------------- 1 | export const enum TX_NOTIFICATION_TYPES { 2 | STANDARD_TX = 'STANDARD_TX', 3 | CONFIRMATION_TX = 'CONFIRMATION_TX', 4 | CANCELLATION_TX = 'CANCELLATION_TX', 5 | SETTINGS_CHANGE_TX = 'SETTINGS_CHANGE_TX', 6 | NEW_SPENDING_LIMIT_TX = 'NEW_SPENDING_LIMIT_TX', 7 | REMOVE_SPENDING_LIMIT_TX = 'REMOVE_SPENDING_LIMIT_TX', 8 | SPENDING_LIMIT_TX = 'SPENDING_LIMIT_TX', 9 | } 10 | -------------------------------------------------------------------------------- /src/logic/tokens/api/fetchSafeCollectibles.ts: -------------------------------------------------------------------------------- 1 | import { getCollectibles, SafeCollectibleResponse } from '@gnosis.pm/safe-react-gateway-sdk' 2 | import { _getChainId } from 'src/config' 3 | import { checksumAddress } from 'src/utils/checksumAddress' 4 | 5 | export const fetchSafeCollectibles = async (safeAddress: string): Promise => { 6 | return getCollectibles(_getChainId(), checksumAddress(safeAddress)) 7 | } 8 | -------------------------------------------------------------------------------- /src/routes/safe/components/AddressBook/EllipsisTransactionDetails/copy.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/OwnerForm/style.ts: -------------------------------------------------------------------------------- 1 | import { lg, md } from 'src/theme/variables' 2 | import { createStyles } from '@material-ui/core' 3 | 4 | export const styles = createStyles({ 5 | formContainer: { 6 | padding: `${md} ${lg}`, 7 | minHeight: '340px', 8 | }, 9 | buttonRow: { 10 | height: '84px', 11 | justifyContent: 'center', 12 | gap: '16px', 13 | }, 14 | }) 15 | -------------------------------------------------------------------------------- /src/utils/events/utils.ts: -------------------------------------------------------------------------------- 1 | import { trackEvent } from 'src/utils/googleTagManager' 2 | 3 | export type TrackEvent = Parameters[0] 4 | 5 | export const addEventCategory = ( 6 | events: Record>, 7 | category: string, 8 | ): Record => { 9 | return Object.entries(events).reduce((events, [key, value]) => ({ ...events, [key]: { ...value, category } }), {}) 10 | } 11 | -------------------------------------------------------------------------------- /src/components/layout/ButtonLink/ButtonLink.test.jsx: -------------------------------------------------------------------------------- 1 | import { render, screen } from 'src/utils/test-utils' 2 | import GnoButtonLink from './index' 3 | 4 | describe('', () => { 5 | it('Should render GnoButtonLink', () => { 6 | render() 7 | 8 | const gnoButtonLinkNode = screen.getByTestId('gno-button-link-id') 9 | 10 | expect(gnoButtonLinkNode).toBeInTheDocument() 11 | }) 12 | }) 13 | -------------------------------------------------------------------------------- /src/routes/safe/components/Balances/SendModal/screens/ReviewCollectible/style.ts: -------------------------------------------------------------------------------- 1 | import { lg, md, sm } from 'src/theme/variables' 2 | import { createStyles } from '@material-ui/core' 3 | 4 | export const styles = createStyles({ 5 | container: { 6 | padding: `${md} ${lg}`, 7 | }, 8 | amount: { 9 | marginLeft: sm, 10 | }, 11 | buttonRow: { 12 | height: '84px', 13 | justifyContent: 'center', 14 | gap: '16px', 15 | }, 16 | }) 17 | -------------------------------------------------------------------------------- /src/routes/safe/components/Balances/SendModal/screens/ReviewSendFundsTx/style.ts: -------------------------------------------------------------------------------- 1 | import { lg, md, sm } from 'src/theme/variables' 2 | import { createStyles } from '@material-ui/core' 3 | 4 | export const styles = createStyles({ 5 | container: { 6 | padding: `${md} ${lg}`, 7 | }, 8 | amount: { 9 | marginLeft: sm, 10 | }, 11 | buttonRow: { 12 | height: '84px', 13 | justifyContent: 'center', 14 | gap: '16px', 15 | }, 16 | }) 17 | -------------------------------------------------------------------------------- /src/routes/safe/components/Balances/utils/setTokenImgToPlaceholder.ts: -------------------------------------------------------------------------------- 1 | import { SyntheticEvent } from 'react' 2 | 3 | import TokenPlaceholder from 'src/routes/safe/components/Balances/assets/token_placeholder.svg' 4 | 5 | export const setImageToPlaceholder = (event: SyntheticEvent): void => { 6 | const img = event.currentTarget 7 | if (!/token_placeholder/.test(img.src)) { 8 | img.src = TokenPlaceholder 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /src/routes/safe/components/Balances/SendModal/screens/AddressBookInput/style.ts: -------------------------------------------------------------------------------- 1 | import { createStyles, makeStyles } from '@material-ui/core' 2 | 3 | export const useTextFieldLabelStyle = makeStyles( 4 | createStyles({ 5 | root: { 6 | overflow: 'hidden', 7 | }, 8 | }), 9 | ) 10 | 11 | export const useTextFieldInputStyle = makeStyles( 12 | createStyles({ 13 | input: { 14 | padding: '16px !important', 15 | }, 16 | }), 17 | ) 18 | -------------------------------------------------------------------------------- /src/routes/safe/components/Balances/SendModal/screens/assets/code.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug-report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create an issue to fix a bug 4 | --- 5 | 6 | 9 | 10 | ## Description 11 | 12 | ## Environment 13 | - Browser: Chrome 14 | - Wallet: MetaMask 15 | - Chain: Rinkeby 16 | 17 | ## Steps to reproduce 18 | 1. Go to 19 | 20 | ## Expected result 21 | 22 | ## Obtained result 23 | 24 | ## Screenshots 25 | -------------------------------------------------------------------------------- /src/routes/safe/components/Settings/ThresholdSettings/ChangeThreshold/style.ts: -------------------------------------------------------------------------------- 1 | import { createStyles, makeStyles } from '@material-ui/core' 2 | 3 | import { lg, md, sm } from 'src/theme/variables' 4 | 5 | export const useStyles = makeStyles( 6 | createStyles({ 7 | modalContent: { 8 | padding: `${md} ${lg}`, 9 | }, 10 | ownersText: { 11 | marginLeft: sm, 12 | }, 13 | inputRow: { 14 | position: 'relative', 15 | }, 16 | }), 17 | ) 18 | -------------------------------------------------------------------------------- /src/routes/safe/components/Transactions/TxList/ThresholdWarning.tsx: -------------------------------------------------------------------------------- 1 | import { ReactElement } from 'react' 2 | import AlertTooltipWarning from './AlertTooltipWarning' 3 | 4 | const ThresholdWarning = (): ReactElement => ( 5 | 10 | ) 11 | 12 | export default ThresholdWarning 13 | -------------------------------------------------------------------------------- /src/utils/decodeTx.ts: -------------------------------------------------------------------------------- 1 | import { getDecodedData, DecodedDataResponse } from '@gnosis.pm/safe-react-gateway-sdk' 2 | 3 | import { _getChainId } from 'src/config' 4 | 5 | export const fetchTxDecoder = async (encodedData: string): Promise => { 6 | if (!encodedData?.length || encodedData === '0x') { 7 | return null 8 | } 9 | 10 | try { 11 | return await getDecodedData(_getChainId(), encodedData) 12 | } catch (error) { 13 | return null 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/utils/getByteLength.ts: -------------------------------------------------------------------------------- 1 | import { hexToBytes } from 'web3-utils' 2 | 3 | export const getByteLength = (data: string | string[]): number => { 4 | try { 5 | if (!Array.isArray(data)) { 6 | data = data.split(',') 7 | } 8 | // Return the sum of the byte sizes of each hex string 9 | return data.reduce((result, hex) => { 10 | const bytes = hexToBytes(hex) 11 | return result + bytes.length 12 | }, 0) 13 | } catch (err) { 14 | return 0 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/components/TextBox/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | import { border } from 'src/theme/variables' 4 | 5 | const Box = styled.p` 6 | padding: 10px; 7 | word-wrap: break-word; 8 | border: solid 2px ${border}; 9 | ` 10 | 11 | type Props = { 12 | children: React.ReactNode 13 | className?: string 14 | } 15 | 16 | const TextBox = ({ children, ...rest }: Props): React.ReactElement => { 17 | return {children} 18 | } 19 | 20 | export default TextBox 21 | -------------------------------------------------------------------------------- /src/logic/config/store/selectors/index.ts: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect' 2 | 3 | import { AppReduxState } from 'src/store' 4 | import { ChainId } from 'src/config/chain.d' 5 | import { CONFIG_REDUCER_ID } from '../reducer' 6 | import { ConfigState } from '../reducer/reducer.d' 7 | 8 | export const configState = (state: AppReduxState): ConfigState => state[CONFIG_REDUCER_ID] 9 | 10 | export const currentChainId = createSelector([configState], (config): ChainId => { 11 | return config.chainId 12 | }) 13 | -------------------------------------------------------------------------------- /src/logic/cookies/store/actions/openCookieBanner.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | 3 | import { OpenCookieBannerPayload } from 'src/logic/cookies/store/reducer/cookies' 4 | 5 | export enum COOKIE_ACTIONS { 6 | OPEN_BANNER = 'cookies/openCookieBanner', 7 | CLOSE_BANNER = 'cookies/closeCookieBanner', 8 | } 9 | 10 | export const openCookieBanner = createAction(COOKIE_ACTIONS.OPEN_BANNER) 11 | export const closeCookieBanner = createAction(COOKIE_ACTIONS.CLOSE_BANNER) 12 | -------------------------------------------------------------------------------- /src/routes/safe/components/Settings/ManageOwners/ReplaceOwnerModal/screens/OwnerForm/style.ts: -------------------------------------------------------------------------------- 1 | import { lg, md, sm } from 'src/theme/variables' 2 | import { createStyles, makeStyles } from '@material-ui/core' 3 | 4 | export const useStyles = makeStyles( 5 | createStyles({ 6 | formContainer: { 7 | padding: `${md} ${lg}`, 8 | minHeight: '340px', 9 | }, 10 | owner: { 11 | alignItems: 'center', 12 | }, 13 | address: { 14 | marginRight: sm, 15 | }, 16 | }), 17 | ) 18 | -------------------------------------------------------------------------------- /src/utils/url.ts: -------------------------------------------------------------------------------- 1 | export const isValidURL = (url: string, protocolsAllowed = ['https:', 'http:']): boolean => { 2 | try { 3 | const urlInfo = new URL(url) 4 | return protocolsAllowed.includes(urlInfo.protocol) 5 | } catch (error) { 6 | return false 7 | } 8 | } 9 | 10 | export const isSameURL = (url1: string, url2: string): boolean => { 11 | try { 12 | const a = new URL(url1) 13 | const b = new URL(url2) 14 | return a.href === b.href 15 | } catch (error) { 16 | return false 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/logic/wallets/utils/unstoppableDomains.ts: -------------------------------------------------------------------------------- 1 | import UnstoppableResolution from '@unstoppabledomains/resolution' 2 | 3 | let UDResolution: UnstoppableResolution 4 | 5 | export const getAddressFromUnstoppableDomain = (name: string): Promise => { 6 | if (!UDResolution) { 7 | UDResolution = new UnstoppableResolution({ 8 | sourceConfig: { 9 | uns: { 10 | api: true, 11 | }, 12 | }, 13 | }) 14 | } 15 | const resolved = UDResolution.addr(name, 'ETH') 16 | return resolved 17 | } 18 | -------------------------------------------------------------------------------- /src/routes/safe/components/Apps/hooks/permissions/usePermissions.ts: -------------------------------------------------------------------------------- 1 | import { useState, useEffect } from 'react' 2 | import local from 'src/utils/storage/local' 3 | 4 | const usePermissions = (storageKey: string): [T, (state: T) => void] => { 5 | const [permissions, setPermissions] = useState((local.getItem(storageKey) || {}) as T) 6 | 7 | useEffect(() => { 8 | local.setItem(storageKey, permissions) 9 | }, [permissions, storageKey]) 10 | 11 | return [permissions, setPermissions] 12 | } 13 | 14 | export { usePermissions } 15 | -------------------------------------------------------------------------------- /src/logic/collectibles/store/actions/addCollectibles.ts: -------------------------------------------------------------------------------- 1 | import { createAction } from 'redux-actions' 2 | 3 | export const ADD_NFT_ASSETS = 'ADD_NFT_ASSETS' 4 | export const ADD_NFT_TOKENS = 'ADD_NFT_TOKENS' 5 | export const SET_NFT_LOADED = 'SET_NFT_LOADED' 6 | 7 | export const addNftAssets = createAction(ADD_NFT_ASSETS, (nftAssets) => ({ 8 | nftAssets, 9 | })) 10 | 11 | export const addNftTokens = createAction(ADD_NFT_TOKENS, (nftTokens) => ({ 12 | nftTokens, 13 | })) 14 | 15 | export const setNftTokensLoaded = createAction(SET_NFT_LOADED) 16 | -------------------------------------------------------------------------------- /src/utils/clipboard.ts: -------------------------------------------------------------------------------- 1 | export const copyToClipboard = (text: string): void => { 2 | const range = document.createRange() 3 | range.selectNodeContents(document.body) 4 | document?.getSelection()?.addRange(range) 5 | 6 | function listener(e: ClipboardEvent) { 7 | e.clipboardData?.setData('text/plain', text) 8 | e.preventDefault() 9 | } 10 | document.addEventListener('copy', listener) 11 | document.execCommand('copy') 12 | document.removeEventListener('copy', listener) 13 | 14 | document?.getSelection()?.removeAllRanges() 15 | } 16 | -------------------------------------------------------------------------------- /src/components/AppLayout/Header/components/CircleDot.tsx: -------------------------------------------------------------------------------- 1 | import * as React from 'react' 2 | import Dot from '@material-ui/icons/FiberManualRecord' 3 | import { getChainById } from 'src/config' 4 | import { ChainId } from 'src/config/chain.d' 5 | 6 | type Props = { 7 | networkId: ChainId 8 | className?: string 9 | } 10 | 11 | export const CircleDot = (props: Props): React.ReactElement => { 12 | const { theme } = getChainById(props.networkId) 13 | 14 | return 15 | } 16 | -------------------------------------------------------------------------------- /src/logic/safe/hooks/useTokenInfo.tsx: -------------------------------------------------------------------------------- 1 | import { useSelector } from 'react-redux' 2 | 3 | import { Token } from 'src/logic/tokens/store/model/token' 4 | import { sameAddress } from 'src/logic/wallets/ethAddresses' 5 | import { safeKnownCoins } from 'src/routes/safe/container/selector' 6 | 7 | const useTokenInfo = (address: string): Token | undefined => { 8 | const tokens = useSelector(safeKnownCoins) 9 | 10 | if (tokens) { 11 | return tokens.find((token) => sameAddress(token.address, address)) 12 | } 13 | } 14 | 15 | export default useTokenInfo 16 | -------------------------------------------------------------------------------- /src/routes/safe/components/Settings/SafeDetails/style.ts: -------------------------------------------------------------------------------- 1 | import { createStyles } from '@material-ui/core/styles' 2 | import { boldFont, border, lg, sm } from 'src/theme/variables' 3 | 4 | export const styles = createStyles({ 5 | formContainer: { 6 | padding: lg, 7 | }, 8 | root: { 9 | display: 'flex', 10 | maxWidth: '480px', 11 | }, 12 | saveBtn: { 13 | fontWeight: boldFont, 14 | marginRight: sm, 15 | }, 16 | controlsRow: { 17 | borderTop: `2px solid ${border}`, 18 | padding: lg, 19 | marginTop: sm, 20 | }, 21 | }) 22 | -------------------------------------------------------------------------------- /src/routes/safe/components/Settings/SpendingLimit/InfoDisplay/DataDisplay.tsx: -------------------------------------------------------------------------------- 1 | import { Text } from '@gnosis.pm/safe-react-components' 2 | import { ReactElement } from 'react' 3 | 4 | interface GenericInfoProps { 5 | title?: string 6 | children: React.ReactNode 7 | } 8 | 9 | const DataDisplay = ({ title, children }: GenericInfoProps): ReactElement => ( 10 | <> 11 | {title && ( 12 | 13 | {title} 14 | 15 | )} 16 | {children} 17 | 18 | ) 19 | 20 | export default DataDisplay 21 | -------------------------------------------------------------------------------- /src/logic/currentSession/store/actions/loadCurrentSessionFromStorage.ts: -------------------------------------------------------------------------------- 1 | import { Dispatch } from 'redux' 2 | 3 | import loadCurrentSession from 'src/logic/currentSession/store/actions/loadCurrentSession' 4 | import { getCurrentSessionFromStorage } from 'src/logic/currentSession/utils' 5 | 6 | const loadCurrentSessionFromStorage = 7 | () => 8 | (dispatch: Dispatch): void => { 9 | const currentSession = getCurrentSessionFromStorage() 10 | 11 | dispatch(loadCurrentSession(currentSession)) 12 | } 13 | 14 | export default loadCurrentSessionFromStorage 15 | -------------------------------------------------------------------------------- /src/routes/safe/components/assets/AddressBookIcon.tsx: -------------------------------------------------------------------------------- 1 | export const AddressBookIcon = (): React.ReactElement => ( 2 | 3 | 4 | 5 | 10 | 11 | 12 | ) 13 | -------------------------------------------------------------------------------- /src/logic/currentSession/utils/index.ts: -------------------------------------------------------------------------------- 1 | import { loadFromStorage, saveToStorage } from 'src/utils/storage' 2 | import { CurrentSessionState } from 'src/logic/currentSession/store/reducer/currentSession' 3 | 4 | const CURRENT_SESSION_STORAGE_KEY = 'CURRENT_SESSION' 5 | 6 | export const getCurrentSessionFromStorage = (): CurrentSessionState | undefined => 7 | loadFromStorage(CURRENT_SESSION_STORAGE_KEY) 8 | 9 | export const saveCurrentSessionToStorage = (currentSession: CurrentSessionState): void => { 10 | saveToStorage(CURRENT_SESSION_STORAGE_KEY, currentSession) 11 | } 12 | -------------------------------------------------------------------------------- /src/logic/tokens/store/selectors/index.ts: -------------------------------------------------------------------------------- 1 | import { createSelector } from 'reselect' 2 | 3 | import { TOKEN_REDUCER_ID, TokenState } from 'src/logic/tokens/store/reducer/tokens' 4 | import { AppReduxState } from 'src/store' 5 | 6 | export const tokensSelector = (state: AppReduxState): TokenState => state[TOKEN_REDUCER_ID] 7 | 8 | export const tokenListSelector = createSelector(tokensSelector, (tokens) => tokens.toList()) 9 | 10 | export const orderedTokenListSelector = createSelector(tokenListSelector, (tokens) => 11 | tokens.sortBy((token) => token.get('symbol')), 12 | ) 13 | -------------------------------------------------------------------------------- /src/routes/safe/components/Transactions/helpers/useSafeTxGas.ts: -------------------------------------------------------------------------------- 1 | import { FEATURES } from '@gnosis.pm/safe-react-gateway-sdk' 2 | import { useSelector } from 'react-redux' 3 | import { currentSafeCurrentVersion } from 'src/logic/safe/store/selectors' 4 | import { hasFeature } from 'src/logic/safe/utils/safeVersion' 5 | 6 | const useSafeTxGas = (): boolean => { 7 | const safeVersion = useSelector(currentSafeCurrentVersion) 8 | const showSafeTxGas = !hasFeature(FEATURES.SAFE_TX_GAS_OPTIONAL, safeVersion) 9 | return showSafeTxGas 10 | } 11 | 12 | export default useSafeTxGas 13 | -------------------------------------------------------------------------------- /src/components/AppLayout/Header/components/ProviderDetails/HidePairingModule.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect } from 'react' 2 | 3 | const HIDE_PAIRING_STYLE = '.bn-onboard-modal-select-wallets li:first-of-type {display: none;}' 4 | 5 | const HidePairingModule = (): null => { 6 | useEffect(() => { 7 | const style = document.createElement('style') 8 | style.innerHTML = HIDE_PAIRING_STYLE 9 | document.head.appendChild(style) 10 | 11 | return () => { 12 | style.remove() 13 | } 14 | }, []) 15 | 16 | return null 17 | } 18 | 19 | export default HidePairingModule 20 | -------------------------------------------------------------------------------- /src/components/layout/ButtonLink/index.tsx: -------------------------------------------------------------------------------- 1 | import cn from 'classnames/bind' 2 | import { ReactElement } from 'react' 3 | 4 | import styles from './index.module.scss' 5 | 6 | const cx = cn.bind(styles) 7 | 8 | const GnoButtonLink = ({ 9 | className = '', 10 | color = 'secondary', 11 | size = 'md', 12 | testId = '', 13 | type = 'button', 14 | weight = 'regular', 15 | ...props 16 | }: any): ReactElement => ( 17 | 17 | ) 18 | } 19 | 20 | export default WalletSwitch 21 | -------------------------------------------------------------------------------- /src/routes/safe/components/Apps/hooks/permissions/index.ts: -------------------------------------------------------------------------------- 1 | import { RestrictedMethods } from '@gnosis.pm/safe-apps-sdk' 2 | import { AllowedFeatures } from '../../types' 3 | 4 | export * from './useBrowserPermissions' 5 | export * from './useSafePermissions' 6 | 7 | export const SAFE_PERMISSIONS_TEXTS = { 8 | [RestrictedMethods.requestAddressBook]: { 9 | displayName: 'Address Book', 10 | description: 'Access to your address book', 11 | }, 12 | } 13 | 14 | const capitalize = (str: string) => str.charAt(0).toUpperCase() + str.slice(1) 15 | 16 | export const BROWSER_PERMISSIONS_TEXTS = Object.values(AllowedFeatures).reduce((acc, feature) => { 17 | acc[feature] = { 18 | displayName: capitalize(feature.toString()), 19 | description: capitalize(feature.toString()), 20 | } 21 | return acc 22 | }, {}) 23 | -------------------------------------------------------------------------------- /src/routes/safe/components/Balances/SendModal/screens/SendFunds/TokenSelectField/style.ts: -------------------------------------------------------------------------------- 1 | import { createStyles, makeStyles } from '@material-ui/core' 2 | 3 | import { sm } from 'src/theme/variables' 4 | 5 | export const useSelectedTokenStyles = makeStyles( 6 | createStyles({ 7 | container: { 8 | background: 'none !important', 9 | padding: '0', 10 | width: '100%', 11 | }, 12 | tokenData: { 13 | padding: 0, 14 | margin: 0, 15 | lineHeight: '14px', 16 | }, 17 | tokenImage: { 18 | display: 'block', 19 | marginRight: sm, 20 | height: 28, 21 | width: 'auto', 22 | }, 23 | }), 24 | ) 25 | 26 | export const useSelectStyles = makeStyles( 27 | createStyles({ 28 | selectMenu: { 29 | paddingRight: 0, 30 | }, 31 | }), 32 | ) 33 | -------------------------------------------------------------------------------- /src/components/ScanQRModal/style.ts: -------------------------------------------------------------------------------- 1 | import { background, lg, secondaryText, sm } from 'src/theme/variables' 2 | import { createStyles } from '@material-ui/core' 3 | 4 | export const styles = createStyles({ 5 | heading: { 6 | padding: lg, 7 | justifyContent: 'space-between', 8 | maxHeight: '75px', 9 | boxSizing: 'border-box', 10 | }, 11 | loaderContainer: { 12 | width: '100%', 13 | height: '100%', 14 | }, 15 | close: { 16 | height: '25px', 17 | width: '25px', 18 | color: secondaryText, 19 | }, 20 | detailsContainer: { 21 | backgroundColor: background, 22 | maxHeight: '450px', 23 | }, 24 | buttonRow: { 25 | height: '84px', 26 | justifyContent: 'center', 27 | }, 28 | button: { 29 | '&:last-child': { 30 | marginLeft: sm, 31 | }, 32 | }, 33 | }) 34 | -------------------------------------------------------------------------------- /src/routes/safe/components/Balances/Coins/styles.ts: -------------------------------------------------------------------------------- 1 | import { background, sm } from 'src/theme/variables' 2 | import { createStyles } from '@material-ui/core' 3 | 4 | export const styles = createStyles({ 5 | iconSmall: { 6 | fontSize: 16, 7 | }, 8 | tooltipInfo: { 9 | position: 'relative', 10 | top: '3px', 11 | left: '3px', 12 | }, 13 | hide: { 14 | '&:hover': { 15 | backgroundColor: `${background}`, 16 | }, 17 | '&:hover $actions': { 18 | visibility: 'initial', 19 | }, 20 | '&:focus $actions': { 21 | visibility: 'initial', 22 | }, 23 | }, 24 | actions: { 25 | justifyContent: 'flex-end', 26 | visibility: 'hidden', 27 | }, 28 | leftIcon: { 29 | marginRight: sm, 30 | }, 31 | currencyValueRow: { 32 | textAlign: 'right', 33 | }, 34 | }) 35 | -------------------------------------------------------------------------------- /src/routes/safe/components/Transactions/TxList/BatchExecuteHoverProvider.tsx: -------------------------------------------------------------------------------- 1 | import { createContext, ReactElement, ReactNode, useState } from 'react' 2 | 3 | export const BatchExecuteHoverContext = createContext<{ 4 | activeHover?: string[] 5 | setActiveHover: (activeHover?: string[]) => void 6 | }>({ 7 | activeHover: undefined, 8 | setActiveHover: () => {}, 9 | }) 10 | 11 | // Used for highlighting transactions that will be included when executing them as a batch 12 | export const BatchExecuteHoverProvider = ({ children }: { children: ReactNode }): ReactElement => { 13 | const [activeHover, setActiveHover] = useState() 14 | 15 | return ( 16 | 17 | {children} 18 | 19 | ) 20 | } 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "baseUrl": ".", 4 | "target": "es5", 5 | "lib": ["dom", "dom.iterable", "esnext"], 6 | "allowJs": true, 7 | "skipLibCheck": true, 8 | "esModuleInterop": true, 9 | "noImplicitAny": false, 10 | "allowSyntheticDefaultImports": true, 11 | "strict": false, 12 | "strictNullChecks": true, 13 | "forceConsistentCasingInFileNames": true, 14 | "module": "esnext", 15 | "moduleResolution": "node", 16 | "resolveJsonModule": true, 17 | "isolatedModules": true, 18 | "noEmit": true, 19 | "jsx": "react-jsx", 20 | "noFallthroughCasesInSwitch": true, 21 | "downlevelIteration": true 22 | }, 23 | "paths": { 24 | "src/*": ["./*"] 25 | }, 26 | "include": ["src/**/*", "public/**/*"], 27 | "exclude": ["node_modules"] 28 | } 29 | -------------------------------------------------------------------------------- /src/components/AppLayout/Header/components/WalletIcon/icons/icon-trezor.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/components/List/ListItemText/index.tsx: -------------------------------------------------------------------------------- 1 | import MuiListItemText from '@material-ui/core/ListItemText' 2 | import { withStyles } from '@material-ui/core/styles' 3 | import * as React from 'react' 4 | 5 | const styles = { 6 | itemTextSecondary: { 7 | textOverflow: 'ellipsis', 8 | overflow: 'hidden', 9 | whiteSpace: 'nowrap', 10 | }, 11 | } 12 | 13 | class ListItemText extends React.PureComponent { 14 | render() { 15 | const { classes, cut = false, primary, secondary } = this.props 16 | 17 | const cutStyle = cut 18 | ? { 19 | secondary: classes.itemTextSecondary, 20 | } 21 | : undefined 22 | 23 | return 24 | } 25 | } 26 | 27 | export default withStyles(styles as any)(ListItemText) 28 | -------------------------------------------------------------------------------- /src/logic/tokens/store/model/token.ts: -------------------------------------------------------------------------------- 1 | import { TokenType } from '@gnosis.pm/safe-react-gateway-sdk' 2 | import { Record, RecordOf } from 'immutable' 3 | 4 | import { BalanceRecord } from 'src/logic/tokens/store/actions/fetchSafeTokens' 5 | 6 | export type TokenProps = { 7 | address: string 8 | name: string 9 | symbol: string 10 | decimals: number | string 11 | logoUri: string | null 12 | balance: BalanceRecord 13 | type?: TokenType 14 | } 15 | 16 | export const makeToken = Record({ 17 | address: '', 18 | name: '', 19 | symbol: '', 20 | decimals: 0, 21 | logoUri: '', 22 | balance: { 23 | fiatBalance: '0', 24 | tokenBalance: '0', 25 | }, 26 | }) 27 | // balance is only set in extendedSafeTokensSelector when we display user's token balances 28 | 29 | export type Token = RecordOf 30 | -------------------------------------------------------------------------------- /src/routes/safe/components/AddressBook/HelpInfo/index.tsx: -------------------------------------------------------------------------------- 1 | import { ReactElement } from 'react' 2 | import styled from 'styled-components' 3 | import { Text, Link, Icon } from '@gnosis.pm/safe-react-components' 4 | 5 | const StyledIcon = styled(Icon)` 6 | svg { 7 | position: relative; 8 | top: 4px; 9 | left: 4px; 10 | } 11 | ` 12 | 13 | const HelpInfo = (): ReactElement => ( 14 | 20 | 21 | Learn about the address book import and export 22 | 23 | 24 | 25 | ) 26 | 27 | export default HelpInfo 28 | -------------------------------------------------------------------------------- /src/components/ChainIndicator/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import styled from 'styled-components' 3 | 4 | import { CircleDot } from 'src/components/AppLayout/Header/components/CircleDot' 5 | import { getChainById } from 'src/config' 6 | import { ChainId } from 'src/config/chain.d' 7 | 8 | interface Props { 9 | chainId: ChainId 10 | hideCircle?: boolean 11 | } 12 | 13 | const Wrapper = styled.span` 14 | & > svg { 15 | font-size: 1.08em; 16 | vertical-align: text-bottom; 17 | margin-right: 0.15em; 18 | } 19 | ` 20 | 21 | const ChainIndicator = ({ chainId, hideCircle }: Props): React.ReactElement => { 22 | return ( 23 | 24 | {!hideCircle && } 25 | {getChainById(chainId).chainName} 26 | 27 | ) 28 | } 29 | 30 | export default ChainIndicator 31 | -------------------------------------------------------------------------------- /src/utils/events/assets.ts: -------------------------------------------------------------------------------- 1 | import { GTM_EVENT } from 'src/utils/googleTagManager' 2 | import { addEventCategory } from 'src/utils/events/utils' 3 | 4 | const ASSETS = { 5 | CURRENCY_MENU: { 6 | event: GTM_EVENT.CLICK, 7 | action: 'Currency menu', 8 | }, 9 | CHANGE_CURRENCY: { 10 | event: GTM_EVENT.META, 11 | action: 'Change currency', 12 | }, 13 | DIFFERING_TOKENS: { 14 | event: GTM_EVENT.META, 15 | action: 'Tokens', 16 | }, 17 | NFT_AMOUNT: { 18 | event: GTM_EVENT.META, 19 | action: 'NFTs', 20 | }, 21 | SEND: { 22 | event: GTM_EVENT.CLICK, 23 | action: 'Send', 24 | }, 25 | RECEIVE: { 26 | event: GTM_EVENT.CLICK, 27 | action: 'Receive', 28 | }, 29 | } 30 | 31 | export const ASSETS_CATEGORY = 'assets' 32 | export const ASSETS_EVENTS = addEventCategory(ASSETS, ASSETS_CATEGORY) 33 | -------------------------------------------------------------------------------- /src/routes/safe/components/Transactions/TxList/assets/filter-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /scripts/github/deploy_release.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Only: 4 | # - Tagged commits 5 | # - Security env variables are available. 6 | if [ -n "$VERSION_TAG" ] && [ -n "$AWS_ACCESS_KEY_ID" ] 7 | then 8 | # Only alphanumeric characters. Example v1.0.0 -> v100 9 | VERSION_TAG_ALPHANUMERIC=$(echo $VERSION_TAG | sed 's/[^a-zA-Z0-9]//g') 10 | 11 | REVIEW_RELEASE_FOLDER="$REPO_NAME_ALPHANUMERIC/$VERSION_TAG_ALPHANUMERIC" 12 | 13 | # Deploy safe-team release project 14 | aws s3 sync build s3://${REVIEW_BUCKET_NAME}/${REVIEW_RELEASE_FOLDER}/app --delete --exclude "*.html" --exclude "/page-data" --cache-control max-age=31536000,public 15 | 16 | aws s3 sync build s3://${REVIEW_BUCKET_NAME}/${REVIEW_RELEASE_FOLDER}/app --delete --exclude "*" --include "*.html" --cache-control max-age=0,no-cache,no-store,must-revalidate --content-type text/html 17 | fi 18 | -------------------------------------------------------------------------------- /src/components/layout/Button/GnoButton.test.tsx: -------------------------------------------------------------------------------- 1 | import { render, fireEvent, screen } from 'src/utils/test-utils' 2 | import GnoButton from './index' 3 | 4 | describe('', () => { 5 | it('Should render GnoButton', () => { 6 | render() 7 | 8 | const gnoButtonNode = screen.getByTestId('gno-button-id') 9 | 10 | expect(gnoButtonNode).toBeInTheDocument() 11 | }) 12 | 13 | it('Should trigger onClick event when clicks on the button', () => { 14 | const onClickSpy = jest.fn() 15 | 16 | render() 17 | 18 | const gnoButtonNode = screen.getByTestId('gno-button-id') 19 | 20 | expect(onClickSpy).not.toHaveBeenCalled() 21 | 22 | fireEvent.click(gnoButtonNode) 23 | 24 | expect(onClickSpy).toHaveBeenCalled() 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /src/logic/safe/store/actions/transactions/utils/transactionHelpers.ts: -------------------------------------------------------------------------------- 1 | import { TypedDataUtils } from 'eth-sig-util' 2 | import { TransactionReceipt } from 'web3-core' 3 | 4 | import { TxArgs } from 'src/logic/safe/store/models/types/transaction' 5 | import { getEip712MessageTypes, generateTypedDataFrom } from 'src/logic/safe/transactions/offchainSigner/EIP712Signer' 6 | 7 | export const generateSafeTxHash = (safeAddress: string, safeVersion: string, txArgs: TxArgs): string => { 8 | const typedData = generateTypedDataFrom({ safeAddress, safeVersion, ...txArgs }) 9 | 10 | const messageTypes = getEip712MessageTypes(safeVersion) 11 | 12 | return `0x${TypedDataUtils.sign(typedData).toString('hex')}` 13 | } 14 | 15 | export const didTxRevert = (receipt: TransactionReceipt): boolean => { 16 | return receipt?.status === false 17 | } 18 | -------------------------------------------------------------------------------- /src/routes/safe/components/Apps/components/PermissionCheckbox.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import { Checkbox, FormControlLabel } from '@material-ui/core' 3 | 4 | type PermissionsCheckboxProps = { 5 | label: string 6 | name: string 7 | checked: boolean 8 | onChange: (event: React.ChangeEvent, checked: boolean) => void 9 | } 10 | 11 | const PermissionsCheckbox = ({ label, checked, onChange, name }: PermissionsCheckboxProps): React.ReactElement => ( 12 | } label={label} /> 13 | ) 14 | 15 | const StyledFormControlLabel = styled(FormControlLabel)` 16 | flex: 1; 17 | .MuiIconButton-root:not(.Mui-checked) { 18 | color: ${({ theme }) => theme.colors.inputDisabled}; 19 | } 20 | ` 21 | export default PermissionsCheckbox 22 | -------------------------------------------------------------------------------- /src/components/AppLayout/Header/components/WalletIcon/icons/icon-safe-mobile.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/utils/isValidAddress.ts: -------------------------------------------------------------------------------- 1 | import { isValidShortChainName } from 'src/routes/routes' 2 | import { isAddress, isHexStrict } from 'web3-utils' 3 | import { parsePrefixedAddress } from './prefixedAddress' 4 | 5 | export const isValidAddress = (address?: string): boolean => { 6 | if (address) { 7 | // `isAddress` do not require the string to start with `0x` 8 | // `isHexStrict` ensures the address to start with `0x` aside from being a valid hex string 9 | return isHexStrict(address) && isAddress(address) 10 | } 11 | 12 | return false 13 | } 14 | 15 | export const isValidPrefixedAddress = (value?: string): boolean => { 16 | if (!value || typeof value !== 'string') { 17 | return false 18 | } 19 | 20 | const { prefix, address } = parsePrefixedAddress(value) 21 | 22 | return isValidShortChainName(prefix) && isValidAddress(address) 23 | } 24 | -------------------------------------------------------------------------------- /src/components/InfoAlert/index.tsx: -------------------------------------------------------------------------------- 1 | import MuiAlert from '@material-ui/lab/Alert' 2 | import useCachedState from 'src/utils/storage/useCachedState' 3 | import Paragraph from 'src/components/layout/Paragraph' 4 | import { ReactElement } from 'react' 5 | 6 | type InfoAlertProps = { 7 | title: string 8 | text: string 9 | id: string 10 | } 11 | 12 | const InfoAlert = (props: InfoAlertProps): ReactElement | null => { 13 | const [isClosed, setClosed] = useCachedState(`${props.id}Closed`) 14 | 15 | return isClosed ? null : ( 16 | setClosed(true)} style={{ margin: '0 20px 26px 0' }}> 17 | {props.title} 18 | 19 | {props.text} 20 | 21 | ) 22 | } 23 | 24 | export default InfoAlert 25 | -------------------------------------------------------------------------------- /src/routes/safe/components/SafeLoadError.tsx: -------------------------------------------------------------------------------- 1 | import { ReactElement } from 'react' 2 | import { useDispatch, useSelector } from 'react-redux' 3 | 4 | import { WELCOME_ROUTE } from 'src/routes/routes' 5 | import removeViewedSafe from 'src/logic/currentSession/store/actions/removeViewedSafe' 6 | import FetchError from './FetchError' 7 | import { currentSafe } from 'src/logic/safe/store/selectors' 8 | 9 | const SafeLoadError = (): ReactElement => { 10 | const dispatch = useDispatch() 11 | 12 | const { address } = useSelector(currentSafe) 13 | const onClick = () => dispatch(removeViewedSafe(address)) 14 | 15 | return ( 16 | 22 | ) 23 | } 24 | 25 | export default SafeLoadError 26 | -------------------------------------------------------------------------------- /cypress/integration/create_safe.spec.js: -------------------------------------------------------------------------------- 1 | describe('Create Safe', () => { 2 | it('should create a new safe', () => { 3 | cy.connectE2EWallet() 4 | 5 | cy.visit('/') 6 | 7 | cy.contains('a', 'Accept all').click() 8 | cy.get('p').contains('Rinkeby').click() 9 | cy.get('[data-testid=connected-wallet]').should('contain', 'E2E Wallet') 10 | cy.contains('Create new Safe').click() 11 | cy.contains('Continue').click() 12 | cy.get('[data-testid=create-safe-name-field]').type('Test Safe') 13 | cy.contains('button', 'Continue').click({ force: true }) 14 | cy.contains('button', 'Continue').click({ force: true }) 15 | 16 | cy.wait(500) // Not sure why without this ends with "Transaction underpriced" 17 | cy.contains('button', 'Create').click() 18 | cy.contains('Your Safe was created successfully', { timeout: 60000 }) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /src/components/DecodeTxs/__tests__/index.ts: -------------------------------------------------------------------------------- 1 | import { getByteLength } from '../../../utils/getByteLength' 2 | 3 | describe('DecodeTxs tests', () => { 4 | it('should calculate the byte length of a single hex string', () => { 5 | expect(getByteLength('0x000000ea')).toBe(4) 6 | }) 7 | 8 | it('should calculate the byte length of multiple hex strings', () => { 9 | expect(getByteLength('0x000000ea,0x48656c6c6f2125')).toBe(11) 10 | }) 11 | 12 | it('should calculate the byte length of an hex array', () => { 13 | expect(getByteLength(['0x000000ea', '0x48656c6c6f2125'])).toBe(11) 14 | }) 15 | 16 | it('should return 0 if passed a non-hex value', () => { 17 | expect(getByteLength('hello')).toBe(0) 18 | }) 19 | 20 | it('should return 0 if passed a non-string', () => { 21 | expect(getByteLength(123 as unknown as string)).toBe(0) 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /src/routes/safe/components/Transactions/TxList/assets/plus-circle-green.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/types/definitions.d.ts: -------------------------------------------------------------------------------- 1 | import 'styled-components' 2 | import { theme } from '@gnosis.pm/safe-react-components' 3 | import { DataLayerArgs } from 'react-gtm-module' 4 | import { BeamerConfig, BeamerMethods } from './Beamer.d' 5 | 6 | type Theme = typeof theme 7 | 8 | export {} 9 | declare global { 10 | interface Window { 11 | isDesktop?: boolean 12 | ethereum?: { 13 | autoRefreshOnNetworkChange: boolean 14 | isMetaMask: boolean 15 | } 16 | beamer_config?: BeamerConfig 17 | Beamer?: BeamerMethods 18 | dataLayer?: DataLayerArgs['dataLayer'] 19 | Cypress? 20 | } 21 | } 22 | declare module '@openzeppelin/contracts/build/contracts/ERC721' 23 | declare module 'currency-flags/dist/currency-flags.min.css' 24 | 25 | declare module 'styled-components' { 26 | export interface DefaultTheme extends Theme {} // eslint-disable-line 27 | } 28 | -------------------------------------------------------------------------------- /src/routes/safe/components/Transactions/TxList/hooks/useHistoryTransactions.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | import { useSelector } from 'react-redux' 3 | 4 | import { TransactionDetails } from 'src/logic/safe/store/models/types/gateway.d' 5 | import { historyTransactions } from 'src/logic/safe/store/selectors/gatewayTransactions' 6 | 7 | export const useHistoryTransactions = (): TransactionDetails => { 8 | const historyTxs = useSelector(historyTransactions) 9 | const [count, setCount] = useState(0) 10 | 11 | useEffect(() => { 12 | const history = historyTxs 13 | ? Object.entries(historyTxs).reduce((acc, [, transactions]) => (acc += transactions.length), 0) 14 | : 0 15 | setCount(history) 16 | }, [historyTxs]) 17 | 18 | return { 19 | count, 20 | transactions: historyTxs ? Object.entries(historyTxs) : [], 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/routes/safe/components/Transactions/TxList/TxInfo.tsx: -------------------------------------------------------------------------------- 1 | import { SettingsChange, TransactionInfo, TransactionStatus } from '@gnosis.pm/safe-react-gateway-sdk' 2 | import { ReactElement } from 'react' 3 | 4 | import { isSettingsChangeTxInfo, isTransferTxInfo } from 'src/logic/safe/store/models/types/gateway.d' 5 | import { TxInfoSettings } from './TxInfoSettings' 6 | import { TxInfoTransfer } from './TxInfoTransfer' 7 | 8 | export const TxInfo = ({ 9 | txInfo, 10 | txStatus, 11 | }: { 12 | txInfo: TransactionInfo 13 | txStatus: TransactionStatus 14 | }): ReactElement | null => { 15 | if (isSettingsChangeTxInfo(txInfo)) { 16 | return 17 | } 18 | 19 | if (isTransferTxInfo(txInfo)) { 20 | return 21 | } 22 | 23 | return null 24 | } 25 | -------------------------------------------------------------------------------- /src/config/__tests__/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { evalTemplate } from '../utils' 2 | 3 | describe('replaceTemplateParams', () => { 4 | it('replace a template param', () => { 5 | const str = 'https://rinkeby.etherscan.io/address/{{address}}' 6 | 7 | expect(evalTemplate(str, { address: '0x123' })).toBe('https://rinkeby.etherscan.io/address/0x123') 8 | }) 9 | it('replaces multiple template params', () => { 10 | const str = 11 | 'https://api-rinkeby.etherscan.io/api?module={{module}}&action={{action}}&address={{address}}&apiKey={{apiKey}}' 12 | 13 | const params = { 14 | module: 'contract', 15 | action: 'getAbi', 16 | address: '0x123', 17 | apiKey: 'test', 18 | } 19 | 20 | expect(evalTemplate(str, params)).toBe( 21 | 'https://api-rinkeby.etherscan.io/api?module=contract&action=getAbi&address=0x123&apiKey=test', 22 | ) 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /src/logic/contracts/__tests__/safeContractErrors.test.ts: -------------------------------------------------------------------------------- 1 | import { decodeMessage } from '../safeContractErrors' 2 | 3 | describe('decodeMessage', () => { 4 | it('returns safe errors', () => { 5 | expect(decodeMessage('GS000: Could not finish initialization')).toBe('GS000: Could not finish initialization') 6 | }) 7 | 8 | it('returns safe errors irregardless of place in error', () => { 9 | expect(decodeMessage('testGS000test')).toBe('GS000: Could not finish initialization') 10 | expect(decodeMessage('test GS000 test')).toBe('GS000: Could not finish initialization') 11 | }) 12 | it('returns safe errors irregardless of case', () => { 13 | expect(decodeMessage('gs000: testing')).toBe('GS000: Could not finish initialization') 14 | }) 15 | 16 | it('returns provided errors if not safe errors', () => { 17 | expect(decodeMessage('Not a Safe error')).toBe(null) 18 | }) 19 | }) 20 | -------------------------------------------------------------------------------- /src/routes/safe/components/Settings/Advanced/style.ts: -------------------------------------------------------------------------------- 1 | import { createStyles, makeStyles } from '@material-ui/core' 2 | 3 | import { background, lg, md } from 'src/theme/variables' 4 | 5 | export const useStyles = makeStyles( 6 | createStyles({ 7 | container: { 8 | padding: lg, 9 | }, 10 | hide: { 11 | '&:hover': { 12 | backgroundColor: `${background}`, 13 | }, 14 | '&:hover $actions': { 15 | visibility: 'initial', 16 | }, 17 | }, 18 | actions: { 19 | justifyContent: 'flex-end', 20 | visibility: 'hidden', 21 | minWidth: '100px', 22 | }, 23 | noBorderBottom: { 24 | '& > td': { 25 | borderBottom: 'none', 26 | }, 27 | }, 28 | modalOwner: { 29 | padding: md, 30 | alignItems: 'center', 31 | }, 32 | modalDescription: { 33 | padding: md, 34 | }, 35 | }), 36 | ) 37 | -------------------------------------------------------------------------------- /patches/web3-eth-ens+1.6.0.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/web3-eth-ens/src/config.js b/node_modules/web3-eth-ens/src/config.js 2 | index b12e5f5..f5baccd 100644 3 | --- a/node_modules/web3-eth-ens/src/config.js 4 | +++ b/node_modules/web3-eth-ens/src/config.js 5 | @@ -30,7 +30,9 @@ var config = { 6 | main: "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", 7 | ropsten: "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", 8 | rinkeby: "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", 9 | - goerli: "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e" 10 | + goerli: "0x00000000000C2E074eC69A0dFb2997BA6C7d2e1e", 11 | + energyWebChain: "0x0A6d64413c07E10E890220BBE1c49170080C6Ca0", 12 | + volta: "0xd7CeF70Ba7efc2035256d828d5287e2D285CD1ac" 13 | }, 14 | // These ids obtained at ensdomains docs: 15 | // https://docs.ens.domains/contract-developer-guide/writing-a-resolver 16 | -------------------------------------------------------------------------------- /src/logic/collectibles/store/actions/fetchCollectibles.ts: -------------------------------------------------------------------------------- 1 | import { Dispatch } from 'redux' 2 | 3 | import { getConfiguredSource } from 'src/logic/collectibles/sources' 4 | import { addNftAssets, addNftTokens, setNftTokensLoaded } from 'src/logic/collectibles/store/actions/addCollectibles' 5 | 6 | export const fetchCollectibles = 7 | (safeAddress: string) => 8 | async (dispatch: Dispatch): Promise => { 9 | dispatch(setNftTokensLoaded(false)) 10 | try { 11 | const source = getConfiguredSource() 12 | const collectibles = await source.fetchCollectibles(safeAddress) 13 | 14 | dispatch(addNftAssets(collectibles.nftAssets)) 15 | dispatch(addNftTokens(collectibles.nftTokens)) 16 | dispatch(setNftTokensLoaded(true)) 17 | } catch (error) { 18 | dispatch(setNftTokensLoaded(false)) 19 | console.log('Error fetching collectibles:', error) 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/components/layout/Table/index.tsx: -------------------------------------------------------------------------------- 1 | import Table from '@material-ui/core/Table' 2 | import TableBody from '@material-ui/core/TableBody' 3 | import TableCell from '@material-ui/core/TableCell' 4 | import TableHead from '@material-ui/core/TableHead' 5 | import TableRow from '@material-ui/core/TableRow' 6 | import { ReactElement } from 'react' 7 | 8 | export { TableBody, TableCell, TableHead, TableRow } 9 | 10 | const buildWidthFrom = (size) => ({ 11 | minWidth: `${size}px`, 12 | }) 13 | 14 | const overflowStyle: any = { 15 | overflowX: 'auto', 16 | } 17 | 18 | // see: https://css-tricks.com/responsive-data-tables/ 19 | const GnoTable = ({ children, size }): ReactElement => { 20 | const style = size ? buildWidthFrom(size) : undefined 21 | 22 | return ( 23 |
24 | {children}
25 |
26 | ) 27 | } 28 | 29 | export default GnoTable 30 | -------------------------------------------------------------------------------- /src/components/CookiesBanner/assets/alert-red.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/logic/cookies/store/reducer/cookies.ts: -------------------------------------------------------------------------------- 1 | import { handleActions } from 'redux-actions' 2 | 3 | import { COOKIE_IDS } from 'src/logic/cookies/model/cookie' 4 | import { COOKIE_ACTIONS } from 'src/logic/cookies/store/actions/openCookieBanner' 5 | 6 | export const COOKIES_REDUCER_ID = 'cookies' 7 | 8 | export type CookieState = { 9 | cookieBannerOpen: boolean 10 | key?: COOKIE_IDS 11 | } 12 | 13 | const initialState: CookieState = { 14 | cookieBannerOpen: false, 15 | key: undefined, 16 | } 17 | 18 | export type OpenCookieBannerPayload = CookieState 19 | 20 | const cookiesReducer = handleActions( 21 | { 22 | [COOKIE_ACTIONS.OPEN_BANNER]: (_, action) => { 23 | return action.payload 24 | }, 25 | [COOKIE_ACTIONS.CLOSE_BANNER]: () => { 26 | return initialState 27 | }, 28 | }, 29 | initialState, 30 | ) 31 | 32 | export default cookiesReducer 33 | -------------------------------------------------------------------------------- /src/components/forms/TextAreaField/index.tsx: -------------------------------------------------------------------------------- 1 | import { createStyles, makeStyles } from '@material-ui/core/styles' 2 | import { ReactElement } from 'react' 3 | 4 | import Field from 'src/components/forms/Field' 5 | import TextField from 'src/components/forms/TextField' 6 | 7 | const styles = createStyles({ 8 | textarea: { 9 | '& > div': { 10 | height: '140px', 11 | paddingTop: '0', 12 | paddingBottom: '0', 13 | alignItems: 'auto', 14 | '& > textarea': { 15 | fontSize: '15px', 16 | letterSpacing: '-0.5px', 17 | lineHeight: '20px', 18 | height: '102px', 19 | }, 20 | }, 21 | }, 22 | }) 23 | 24 | const useStyles = makeStyles(styles) 25 | 26 | export const TextAreaField = ({ ...props }): ReactElement => { 27 | const classes = useStyles() 28 | return 29 | } 30 | -------------------------------------------------------------------------------- /src/components/ChainIndicator/index.test.tsx: -------------------------------------------------------------------------------- 1 | import { ChainId } from 'src/config/chain.d' 2 | import { render } from 'src/utils/test-utils' 3 | import ChainIndicator from '.' 4 | 5 | describe('', () => { 6 | it('renders Rinkeby indicator', () => { 7 | const { container } = render() 8 | const icon = container.querySelector('svg') 9 | const label = (container.textContent || '').trim() 10 | 11 | expect(icon?.getAttribute('color')).toBe('#E8673C') 12 | expect(label).toBe('Rinkeby') 13 | }) 14 | 15 | it('renders Polygon indicator', () => { 16 | const { container } = render() 17 | const icon = container.querySelector('svg') 18 | const label = (container.textContent || '').trim() 19 | 20 | expect(icon?.getAttribute('color')).toBe('#8248E5') 21 | expect(label).toBe('Polygon') 22 | }) 23 | }) 24 | -------------------------------------------------------------------------------- /src/logic/hooks/useEstimationStatus.tsx: -------------------------------------------------------------------------------- 1 | import { Dispatch, SetStateAction, useEffect, useState } from 'react' 2 | 3 | import { EstimationStatus } from './useEstimateTransactionGas' 4 | import { ButtonStatus } from 'src/components/Modal' 5 | 6 | export const useEstimationStatus = ( 7 | txEstimationStatus: EstimationStatus, 8 | ): [buttonStatus: ButtonStatus, setButtonStatus: Dispatch>] => { 9 | const [buttonStatus, setButtonStatus] = useState(ButtonStatus.DISABLED) 10 | 11 | useEffect(() => { 12 | let mounted = true 13 | 14 | if (txEstimationStatus === EstimationStatus.LOADING) { 15 | mounted && setButtonStatus(ButtonStatus.LOADING) 16 | } else { 17 | mounted && setButtonStatus(ButtonStatus.READY) 18 | } 19 | 20 | return () => { 21 | mounted = false 22 | } 23 | }, [txEstimationStatus]) 24 | 25 | return [buttonStatus, setButtonStatus] 26 | } 27 | -------------------------------------------------------------------------------- /src/routes/safe/components/assets/TransactionsIcon.tsx: -------------------------------------------------------------------------------- 1 | export const TransactionsIcon = (): React.ReactElement => ( 2 | 3 | 4 | 5 | 9 | 10 | 11 | ) 12 | -------------------------------------------------------------------------------- /src/logic/hooks/useTxStatus.ts: -------------------------------------------------------------------------------- 1 | import { useSelector } from 'react-redux' 2 | import { TransactionStatus } from '@gnosis.pm/safe-react-gateway-sdk' 3 | import { Transaction } from 'src/logic/safe/store/models/types/gateway.d' 4 | import { AppReduxState } from 'src/store' 5 | import { selectTxStatus } from 'src/logic/safe/store/selectors/pendingTransactions' 6 | import { useState } from 'react' 7 | import { useDebounce } from './useDebounce' 8 | 9 | // Takes into account whether a transaction is pending or not 10 | const useTxStatus = (transaction: Transaction): TransactionStatus => { 11 | const storedStatus = useSelector((state: AppReduxState) => selectTxStatus(state, transaction)) 12 | const [localStatus, setLocalStatus] = useState(storedStatus) 13 | 14 | useDebounce(() => { 15 | if (storedStatus) { 16 | setLocalStatus(storedStatus) 17 | } 18 | }, 100) 19 | 20 | return localStatus 21 | } 22 | 23 | export default useTxStatus 24 | -------------------------------------------------------------------------------- /src/logic/safe/transactions/api/fetchSafeTransaction.ts: -------------------------------------------------------------------------------- 1 | import { getTransactionDetails, TransactionDetails } from '@gnosis.pm/safe-react-gateway-sdk' 2 | 3 | import { _getChainId } from 'src/config' 4 | 5 | // Cache the request promise to avoid simulateneous requests 6 | // It's cleared as soon as the promise is resolved 7 | const cache = {} 8 | 9 | /** 10 | * @param {string} txId safeTxHash or transaction id from client-gateway 11 | */ 12 | export const fetchSafeTransaction = async (txId: string): Promise => { 13 | const chainId = _getChainId() 14 | const cacheKey = `${chainId}_${txId}` 15 | 16 | const promise: Promise = cache[cacheKey] || getTransactionDetails(chainId, txId) 17 | 18 | // Save the promise into cache 19 | cache[cacheKey] = promise 20 | 21 | // Clear cache when promise finishes 22 | promise.catch(() => null).then(() => delete cache[cacheKey]) 23 | 24 | return promise 25 | } 26 | -------------------------------------------------------------------------------- /src/routes/LoadSafePage/fields/loadFields.tsx: -------------------------------------------------------------------------------- 1 | export const FIELD_LOAD_CUSTOM_SAFE_NAME = 'customSafeName' 2 | export const FIELD_LOAD_SUGGESTED_SAFE_NAME = 'suggestedSafeName' 3 | export const FIELD_LOAD_SAFE_ADDRESS = 'safeAddress' 4 | export const FIELD_LOAD_IS_LOADING_SAFE_ADDRESS = 'isLoadingSafeAddress' 5 | export const FIELD_SAFE_OWNER_LIST = 'safeOwnerList' 6 | export const FIELD_SAFE_OWNER_ENS_LIST = 'safeOwnerENSList' 7 | export const FIELD_SAFE_THRESHOLD = 'safeThreshold' 8 | 9 | export type OwnerFieldListItem = { 10 | address: string 11 | name: string 12 | } 13 | 14 | export type LoadSafeFormValues = { 15 | [FIELD_LOAD_CUSTOM_SAFE_NAME]?: string 16 | [FIELD_LOAD_SUGGESTED_SAFE_NAME]: string 17 | [FIELD_LOAD_SAFE_ADDRESS]?: string 18 | [FIELD_LOAD_IS_LOADING_SAFE_ADDRESS]: boolean 19 | [FIELD_SAFE_OWNER_LIST]: Array 20 | [FIELD_SAFE_OWNER_ENS_LIST]: Record 21 | [FIELD_SAFE_THRESHOLD]?: number 22 | } 23 | -------------------------------------------------------------------------------- /src/routes/safe/components/Transactions/TxList/assets/check-circle-green.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /patches/web3-eth+1.6.0.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/web3-eth/src/getNetworkType.js b/node_modules/web3-eth/src/getNetworkType.js 2 | index 5f9b9b3..8c83b18 100644 3 | --- a/node_modules/web3-eth/src/getNetworkType.js 4 | +++ b/node_modules/web3-eth/src/getNetworkType.js 5 | @@ -61,6 +61,14 @@ var getNetworkType = function (callback) { 6 | id === 42) { 7 | returnValue = 'kovan'; 8 | } 9 | + if (genesis.hash === '0x0b6d3e680af2fc525392c720666cce58e3d8e6fe75ba4b48cb36bcc69039229b' && 10 | + id === 246) { 11 | + returnValue = 'energyWebChain'; 12 | + } 13 | + if (genesis.hash === '0xebd8b413ca7b7f84a8dd20d17519ce2b01954c74d94a0a739a3e416abe0e43e5' && 14 | + id === 73799) { 15 | + returnValue = 'volta'; 16 | + } 17 | 18 | if (typeof callback === 'function') { 19 | callback(null, returnValue); 20 | -------------------------------------------------------------------------------- /src/logic/tokens/store/reducer/tokens.ts: -------------------------------------------------------------------------------- 1 | import { List, Map } from 'immutable' 2 | import { Action, handleActions } from 'redux-actions' 3 | 4 | import { ADD_TOKENS } from 'src/logic/tokens/store/actions/addTokens' 5 | import { Token } from 'src/logic/tokens/store/model/token' 6 | 7 | export const TOKEN_REDUCER_ID = 'tokens' 8 | 9 | export type TokenState = Map 10 | 11 | type TokensPayload = { tokens: List } 12 | type TokenPayload = { token: Token } 13 | type Payloads = TokensPayload | TokenPayload 14 | 15 | const tokensReducer = handleActions( 16 | { 17 | [ADD_TOKENS]: (state, action: Action) => { 18 | const { tokens } = action.payload 19 | 20 | return state.withMutations((map) => { 21 | tokens.forEach((token: Token) => { 22 | map.set(token.address, token) 23 | }) 24 | }) 25 | }, 26 | }, 27 | Map(), 28 | ) 29 | 30 | export default tokensReducer 31 | -------------------------------------------------------------------------------- /src/assets/icons/info_red.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /src/routes/safe/components/Apps/components/ThirdPartyCookiesWarning.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import MuiAlert from '@material-ui/lab/Alert' 3 | import MuiAlertTitle from '@material-ui/lab/AlertTitle' 4 | import { Link } from '@gnosis.pm/safe-react-components' 5 | 6 | type Props = { 7 | onClose: () => void 8 | } 9 | 10 | const HELP_LINK = 11 | 'https://help.gnosis-safe.io/en/articles/5955031-why-do-i-need-to-enable-third-party-cookies-for-safe-apps' 12 | 13 | export const ThirdPartyCookiesWarning = ({ onClose }: Props): React.ReactElement => { 14 | return ( 15 | 16 | 17 | Third party cookies are disabled. Safe Apps may therefore not work properly. You can find out more information 18 | about this{' '} 19 | 20 | here 21 | 22 | 23 | 24 | ) 25 | } 26 | -------------------------------------------------------------------------------- /src/routes/safe/components/Settings/SpendingLimit/InfoDisplay/ResetTimeInfo.tsx: -------------------------------------------------------------------------------- 1 | import { IconText, Text } from '@gnosis.pm/safe-react-components' 2 | import { ThemeColors } from '@gnosis.pm/safe-react-components/dist/theme' 3 | import { ReactElement } from 'react' 4 | 5 | import Row from 'src/components/layout/Row' 6 | 7 | interface ResetTimeInfoProps { 8 | title?: string 9 | label?: string 10 | color?: ThemeColors 11 | } 12 | 13 | const ResetTimeInfo = ({ title, label, color }: ResetTimeInfoProps): ReactElement => ( 14 | <> 15 | 16 | {title} 17 | 18 | {label ? ( 19 | 20 | 21 | 22 | ) : ( 23 | 24 | One-time spending limit 25 | 26 | )} 27 | 28 | ) 29 | 30 | export default ResetTimeInfo 31 | -------------------------------------------------------------------------------- /src/assets/icons/error.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/logic/wallets/e2e-wallet/module.ts: -------------------------------------------------------------------------------- 1 | import HDWalletProvider from '@truffle/hdwallet-provider' 2 | import { WalletModule } from 'bnc-onboard/dist/src/interfaces' 3 | import { getRpcServiceUrl } from 'src/config' 4 | import CypressLogo from 'src/assets/icons/cypress_logo.svg' 5 | 6 | const WALLET_NAME = 'E2E Wallet' 7 | 8 | const getE2EWalletModule = (): WalletModule => { 9 | return { 10 | name: WALLET_NAME, 11 | type: 'injected', 12 | iconSrc: CypressLogo, 13 | wallet: async (helpers) => { 14 | const { createModernProviderInterface } = helpers 15 | const provider = new HDWalletProvider({ 16 | mnemonic: window.Cypress.env('CYPRESS_MNEMONIC'), 17 | providerOrUrl: getRpcServiceUrl(), 18 | }) 19 | return { 20 | provider, 21 | interface: createModernProviderInterface(provider), 22 | } 23 | }, 24 | desktop: true, 25 | mobile: true, 26 | } 27 | } 28 | 29 | export default getE2EWalletModule 30 | -------------------------------------------------------------------------------- /src/components/Divider/index.tsx: -------------------------------------------------------------------------------- 1 | import { ReactElement } from 'react' 2 | import styled from 'styled-components' 3 | import { Icon, Divider as DividerSRC } from '@gnosis.pm/safe-react-components' 4 | 5 | const Wrapper = styled.div` 6 | position: relative; 7 | display: flex; 8 | align-items: center; 9 | margin: 8px 0; 10 | 11 | svg { 12 | margin: 0 12px 0 4px; 13 | } 14 | ` 15 | const StyledDivider = styled(DividerSRC)` 16 | width: 100%; 17 | ` 18 | 19 | const StyledIcon = styled(Icon)` 20 | position: absolute; 21 | left: 50%; 22 | transform: translateX(-50%); 23 | padding: 0 20px; 24 | background: white; 25 | 26 | & svg { 27 | margin: 0; 28 | } 29 | ` 30 | 31 | type Props = { 32 | withArrow?: boolean 33 | } 34 | 35 | const Divider = ({ withArrow }: Props): ReactElement => ( 36 | 37 | {withArrow && } 38 | 39 | 40 | ) 41 | 42 | export default Divider 43 | -------------------------------------------------------------------------------- /src/routes/opening/utils/getSafeAddressFromLogs.ts: -------------------------------------------------------------------------------- 1 | import abiDecoder from 'abi-decoder' 2 | import { getProxyFactoryDeployment } from '@gnosis.pm/safe-deployments' 3 | import { Log } from 'web3-core' 4 | import { checksumAddress } from 'src/utils/checksumAddress' 5 | 6 | import { LATEST_SAFE_VERSION } from 'src/utils/constants' 7 | 8 | // Init abiDecoder with ProxyCreation ABI 9 | abiDecoder.addABI( 10 | getProxyFactoryDeployment({ 11 | version: LATEST_SAFE_VERSION, 12 | })?.abi, 13 | ) 14 | 15 | export const getNewSafeAddressFromLogs = (logs: Log[]): string => { 16 | // We find the ProxyCreation event in the logs 17 | const proxyCreationEvent = abiDecoder.decodeLogs(logs).find(({ name }) => name === 'ProxyCreation') 18 | 19 | // We extract the proxy creation information from the event parameters 20 | const proxyInformation = proxyCreationEvent?.events?.find(({ name }) => name === 'proxy') 21 | 22 | return checksumAddress(proxyInformation?.value || '') 23 | } 24 | -------------------------------------------------------------------------------- /src/routes/safe/components/Transactions/helpers/EditTxParametersForm/style.ts: -------------------------------------------------------------------------------- 1 | import { lg, md, secondaryText, sm } from 'src/theme/variables' 2 | import { createStyles } from '@material-ui/core' 3 | 4 | export const styles = createStyles({ 5 | heading: { 6 | padding: `${md} ${lg}`, 7 | justifyContent: 'space-between', 8 | boxSizing: 'border-box', 9 | height: '74px', 10 | }, 11 | annotation: { 12 | letterSpacing: '-1px', 13 | color: secondaryText, 14 | marginRight: 'auto', 15 | marginLeft: '20px', 16 | }, 17 | headingText: { 18 | fontSize: lg, 19 | }, 20 | closeIcon: { 21 | height: '24px', 22 | width: '24px', 23 | }, 24 | container: { 25 | padding: `${md} ${lg}`, 26 | }, 27 | amount: { 28 | marginLeft: sm, 29 | }, 30 | address: { 31 | marginRight: sm, 32 | }, 33 | buttonRow: { 34 | height: '52px', 35 | justifyContent: 'center', 36 | alignItems: 'center', 37 | gap: '16px', 38 | }, 39 | }) 40 | -------------------------------------------------------------------------------- /src/logic/tokens/store/actions/fetchTokens.ts: -------------------------------------------------------------------------------- 1 | import ERC20Contract from '@openzeppelin/contracts/build/contracts/ERC20.json' 2 | import ERC721Contract from '@openzeppelin/contracts/build/contracts/ERC721.json' 3 | import { AbiItem } from 'web3-utils' 4 | 5 | import { ERC20 } from 'src/types/contracts/ERC20.d' 6 | import { ERC721 } from 'src/types/contracts/ERC721.d' 7 | import { getWeb3 } from 'src/logic/wallets/getWeb3' 8 | 9 | const createERC20TokenContract = (tokenAddress: string): ERC20 => { 10 | const web3 = getWeb3() 11 | return new web3.eth.Contract(ERC20Contract.abi as AbiItem[], tokenAddress) as unknown as ERC20 12 | } 13 | 14 | const createERC721TokenContract = (tokenAddress: string): ERC721 => { 15 | const web3 = getWeb3() 16 | return new web3.eth.Contract(ERC721Contract.abi as AbiItem[], tokenAddress) as unknown as ERC721 17 | } 18 | 19 | export const getERC20TokenContract = createERC20TokenContract 20 | 21 | export const getERC721TokenContract = createERC721TokenContract 22 | -------------------------------------------------------------------------------- /src/routes/safe/components/Transactions/TxList/assets/settings.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/logic/currencyValues/store/actions/updateAvailableCurrencies.ts: -------------------------------------------------------------------------------- 1 | import { Action } from 'redux-actions' 2 | import { ThunkDispatch } from 'redux-thunk' 3 | import { AppReduxState } from 'src/store' 4 | import { AvailableCurrenciesPayload } from 'src/logic/currencyValues/store/reducer/currencyValues' 5 | import { setAvailableCurrencies } from 'src/logic/currencyValues/store/actions/setAvailableCurrencies' 6 | import { getFiatCurrencies } from '@gnosis.pm/safe-react-gateway-sdk' 7 | import { Errors, logError } from 'src/logic/exceptions/CodedException' 8 | 9 | export const updateAvailableCurrencies = 10 | () => 11 | async (dispatch: ThunkDispatch>): Promise => { 12 | try { 13 | const availableCurrencies = await getFiatCurrencies() 14 | dispatch(setAvailableCurrencies({ availableCurrencies })) 15 | } catch (err) { 16 | logError(Errors._607, err.message) 17 | } 18 | return Promise.resolve() 19 | } 20 | -------------------------------------------------------------------------------- /src/components/AppLayout/Sidebar/Threshold/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | 3 | import { primaryLite, primaryActive } from 'src/theme/variables' 4 | 5 | const Container = styled.div<{ size: number }>` 6 | background: ${primaryLite}; 7 | color: ${primaryActive}; 8 | font-size: ${(p) => p.size}px; 9 | font-weight: bold; 10 | border-radius: 100%; 11 | padding: 0.25em; 12 | position: absolute; 13 | z-index: 2; 14 | top: -8px; 15 | left: -8px; 16 | min-width: 1.5em; 17 | min-height: 1.5em; 18 | display: flex; 19 | align-items: center; 20 | justify-content: center; 21 | line-height: 1; 22 | ` 23 | 24 | type ThresholdProps = { 25 | threshold: number 26 | owners: number 27 | size?: number 28 | } 29 | 30 | const Threshold = ({ threshold, owners, size = 12 }: ThresholdProps): React.ReactElement | null => { 31 | return ( 32 | 33 | {threshold}/{owners} 34 | 35 | ) 36 | } 37 | 38 | export default Threshold 39 | -------------------------------------------------------------------------------- /src/components/AppLayout/Sidebar/DebugToggle/index.tsx: -------------------------------------------------------------------------------- 1 | import styled from 'styled-components' 2 | import FormControlLabel from '@material-ui/core/FormControlLabel/FormControlLabel' 3 | import { Switch } from '@gnosis.pm/safe-react-components' 4 | import useCachedState from 'src/utils/storage/useCachedState' 5 | import { LS_USE_PROD_CGW } from 'src/utils/constants' 6 | 7 | const StyledContainer = styled.div` 8 | padding-top: 10px; 9 | margin-left: 16px; 10 | ` 11 | 12 | const DebugToggle = (): React.ReactElement => { 13 | const [enabled = false, setEnabled] = useCachedState(LS_USE_PROD_CGW) 14 | 15 | const onToggle = () => { 16 | setEnabled((prev: boolean) => !prev) 17 | 18 | setTimeout(() => { 19 | location.reload() 20 | }, 200) 21 | } 22 | 23 | return ( 24 | 25 | } label="Use prod CGW" /> 26 | 27 | ) 28 | } 29 | 30 | export default DebugToggle 31 | -------------------------------------------------------------------------------- /src/components/forms/GnoForm/index.tsx: -------------------------------------------------------------------------------- 1 | import { ReactElement } from 'react' 2 | import { Form } from 'react-final-form' 3 | 4 | const stylesBasedOn = (padding) => ({ 5 | padding: `0 ${padding}%`, 6 | flexDirection: 'column', 7 | flex: '1 0 auto', 8 | }) 9 | 10 | const GnoForm = ({ 11 | children, 12 | decorators, 13 | formMutators, 14 | initialValues, 15 | onSubmit, 16 | padding = 0, 17 | subscription, 18 | testId = '', 19 | validation, 20 | }: any): ReactElement => ( 21 |
( 27 | 28 | {children(rest.submitting, rest.validating, rest, rest.form.mutators)} 29 |
30 | )} 31 | subscription={subscription} 32 | validate={validation} 33 | /> 34 | ) 35 | 36 | export default GnoForm 37 | -------------------------------------------------------------------------------- /src/logic/safe/hooks/useSafeScheduledUpdates.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | import { useDispatch, useSelector } from 'react-redux' 3 | import { currentChainId } from 'src/logic/config/store/selectors' 4 | import { fetchSafe } from 'src/logic/safe/store/actions/fetchSafe' 5 | import { SAFE_POLLING_INTERVAL } from 'src/utils/constants' 6 | 7 | export const useSafeScheduledUpdates = (safeAddress?: string, isInitialLoad = false): void => { 8 | const dispatch = useDispatch() 9 | const [pollCount, setPollCount] = useState(0) 10 | const chainId = useSelector(currentChainId) 11 | 12 | useEffect(() => { 13 | const timer = setTimeout(() => { 14 | if (safeAddress) { 15 | dispatch(fetchSafe(safeAddress, isInitialLoad)) 16 | } 17 | setPollCount((prev) => prev + 1) 18 | }, SAFE_POLLING_INTERVAL) 19 | 20 | return () => { 21 | clearTimeout(timer) 22 | } 23 | }, [dispatch, safeAddress, chainId, pollCount, setPollCount, isInitialLoad]) 24 | } 25 | -------------------------------------------------------------------------------- /src/routes/safe/components/Transactions/__tests__/utils.test.ts: -------------------------------------------------------------------------------- 1 | import { getInteractionTitle } from '../helpers/utils' 2 | 3 | describe('getInteractionTitle', () => { 4 | it('should return no amount relevant title for a numeric 0 amount', () => { 5 | expect(getInteractionTitle(0)).toEqual('Interact with:') 6 | }) 7 | it('should return no amount relevant title for a string 0 amount', () => { 8 | expect(getInteractionTitle('0')).toEqual('Interact with:') 9 | }) 10 | it('should return an amount relevant title for a numeric amount', () => { 11 | expect(getInteractionTitle(1)).toEqual(`Interact with (and send 1 ETH to):`) 12 | expect(getInteractionTitle(0.1)).toEqual(`Interact with (and send 0.1 ETH to):`) 13 | }) 14 | it('should return an amount relevant title for a string amount', () => { 15 | expect(getInteractionTitle('1')).toEqual(`Interact with (and send 1 ETH to):`) 16 | expect(getInteractionTitle('0.1')).toEqual(`Interact with (and send 0.1 ETH to):`) 17 | }) 18 | }) 19 | -------------------------------------------------------------------------------- /src/routes/safe/components/Transactions/helpers/Simulation/simulation.ts: -------------------------------------------------------------------------------- 1 | import { 2 | TenderlySimulatePayload, 3 | TenderlySimulation, 4 | } from 'src/routes/safe/components/Transactions/helpers/Simulation/types' 5 | import axios from 'axios' 6 | import { TENDERLY_ORG_NAME, TENDERLY_PROJECT_NAME, TENDERLY_SIMULATE_ENDPOINT_URL } from 'src/utils/constants' 7 | 8 | const getSimulation = async (tx: TenderlySimulatePayload): Promise => { 9 | const response = await axios.post(TENDERLY_SIMULATE_ENDPOINT_URL, tx) 10 | 11 | return response.data 12 | } 13 | 14 | const isSimulationEnvSet = 15 | Boolean(TENDERLY_SIMULATE_ENDPOINT_URL) && Boolean(TENDERLY_ORG_NAME) && Boolean(TENDERLY_PROJECT_NAME) 16 | 17 | const getSimulationLink = (simulationId: string): string => { 18 | return `https://dashboard.tenderly.co/public/${TENDERLY_ORG_NAME}/${TENDERLY_PROJECT_NAME}/simulator/${simulationId}` 19 | } 20 | 21 | export { getSimulationLink, getSimulation, isSimulationEnvSet } 22 | -------------------------------------------------------------------------------- /src/logic/safe/api/__tests__/fetchTokenCurrenciesBalances.test.ts: -------------------------------------------------------------------------------- 1 | import { fetchTokenCurrenciesBalances } from 'src/logic/safe/api/fetchTokenCurrenciesBalances' 2 | import { _getChainId } from 'src/config' 3 | import { getBalances } from '@gnosis.pm/safe-react-gateway-sdk' 4 | 5 | jest.mock('@gnosis.pm/safe-react-gateway-sdk', () => ({ 6 | getBalances: jest.fn(() => Promise.resolve({ success: true })), 7 | })) 8 | 9 | describe('fetchTokenCurrenciesBalances', () => { 10 | const safeAddress = '0xdfA693da0D16F5E7E78FdCBeDe8FC6eBEa44f1Cf' 11 | const excludeSpamTokens = true 12 | 13 | it('Given a safe address, calls the API and returns token balances', () => { 14 | fetchTokenCurrenciesBalances({ 15 | safeAddress, 16 | excludeSpamTokens, 17 | selectedCurrency: 'USD', 18 | }) 19 | 20 | expect(getBalances).toHaveBeenCalledWith(_getChainId(), '0xdfA693da0D16F5E7E78FdCBeDe8FC6eBEa44f1Cf', 'USD', { 21 | exclude_spam: true, 22 | trusted: false, 23 | }) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /src/routes/safe/components/Settings/assets/icons/RequiredConfirmationsIcon.tsx: -------------------------------------------------------------------------------- 1 | export const RequiredConfirmationsIcon = (): React.ReactElement => ( 2 | 3 | 4 | 5 | 9 | 10 | 11 | ) 12 | -------------------------------------------------------------------------------- /src/components/layout/Block/index.module.scss: -------------------------------------------------------------------------------- 1 | @import "src/theme/variables.scss"; 2 | 3 | .block { 4 | } 5 | 6 | .xs { 7 | margin-bottom: $xs; 8 | } 9 | 10 | .sm { 11 | margin-bottom: $sm; 12 | } 13 | 14 | .md { 15 | margin-bottom: $md; 16 | } 17 | 18 | .lg { 19 | margin-bottom: $lg; 20 | } 21 | 22 | .xl { 23 | margin-bottom: $xl; 24 | } 25 | 26 | .paddingXs { 27 | padding-top: $xs; 28 | } 29 | 30 | .paddingSm { 31 | padding-top: $sm; 32 | } 33 | 34 | .paddingMd { 35 | padding-top: $md; 36 | } 37 | 38 | .paddingLg { 39 | padding-top: $lg; 40 | } 41 | 42 | .paddingXl { 43 | padding-top: $xl; 44 | } 45 | 46 | .center { 47 | display: flex; 48 | align-items: center; 49 | justify-content: center; 50 | } 51 | 52 | .left { 53 | display: flex; 54 | align-items: center; 55 | } 56 | 57 | .space-around { 58 | display: flex; 59 | align-items: center; 60 | justify-content: space-around; 61 | } 62 | 63 | .right { 64 | display: flex; 65 | align-items: center; 66 | justify-content: flex-end; 67 | } -------------------------------------------------------------------------------- /src/logic/safe/transactions/awaitingTransactions.ts: -------------------------------------------------------------------------------- 1 | import { TransactionSummary } from '@gnosis.pm/safe-react-gateway-sdk' 2 | 3 | import { isMultisigExecutionInfo, LocalTransactionStatus } from 'src/logic/safe/store/models/types/gateway.d' 4 | import { addressInList } from 'src/routes/safe/components/Transactions/TxList/utils' 5 | 6 | export const getAwaitingGatewayTransactions = ( 7 | allTransactions: TransactionSummary[], 8 | userAccount: string, 9 | ): TransactionSummary[] => { 10 | return allTransactions.filter((tx) => { 11 | // The transaction is not executed and is not cancelled, nor pending, so it's still waiting confirmations 12 | if (tx.txStatus === LocalTransactionStatus.AWAITING_CONFIRMATIONS && isMultisigExecutionInfo(tx.executionInfo)) { 13 | // Then we check if the waiting confirmations are not from the current user, otherwise, filters this transaction 14 | return addressInList(tx.executionInfo?.missingSigners ?? undefined)(userAccount) 15 | } 16 | 17 | return false 18 | }) 19 | } 20 | -------------------------------------------------------------------------------- /src/logic/safe/api/fetchTokenCurrenciesBalances.ts: -------------------------------------------------------------------------------- 1 | import { getBalances, SafeBalanceResponse, TokenInfo } from '@gnosis.pm/safe-react-gateway-sdk' 2 | import { _getChainId } from 'src/config' 3 | import { checksumAddress } from 'src/utils/checksumAddress' 4 | 5 | export type TokenBalance = { 6 | tokenInfo: TokenInfo 7 | balance: string 8 | fiatBalance: string 9 | fiatConversion: string 10 | } 11 | 12 | type FetchTokenCurrenciesBalancesProps = { 13 | safeAddress: string 14 | selectedCurrency: string 15 | excludeSpamTokens?: boolean 16 | trustedTokens?: boolean 17 | } 18 | 19 | export const fetchTokenCurrenciesBalances = async ({ 20 | safeAddress, 21 | selectedCurrency, 22 | excludeSpamTokens = true, 23 | trustedTokens = false, 24 | }: FetchTokenCurrenciesBalancesProps): Promise => { 25 | const address = checksumAddress(safeAddress) 26 | return getBalances(_getChainId(), address, selectedCurrency, { 27 | exclude_spam: excludeSpamTokens, 28 | trusted: trustedTokens, 29 | }) 30 | } 31 | -------------------------------------------------------------------------------- /src/routes/safe/components/Settings/assets/icons/OwnersIcon.tsx: -------------------------------------------------------------------------------- 1 | export const OwnersIcon = (): React.ReactElement => ( 2 | 3 | 4 | 5 | 9 | 10 | 11 | ) 12 | -------------------------------------------------------------------------------- /src/routes/safe/components/Transactions/TxList/assets/circle-cross-red.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.storybook/preview.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { MemoryRouter } from 'react-router-dom' 3 | import { Provider } from 'react-redux' 4 | import { addDecorator } from '@storybook/react' 5 | import { ThemeProvider, createGlobalStyle } from 'styled-components' 6 | import { theme } from '@gnosis.pm/safe-react-components' 7 | 8 | import { aNewStore } from 'src/store' 9 | import averta from 'src/assets/fonts/Averta-normal.woff2' 10 | import avertaBold from 'src/assets/fonts/Averta-ExtraBold.woff2' 11 | 12 | const GlobalStyles = createGlobalStyle` 13 | @font-face { 14 | font-family: 'Averta'; 15 | src: local('Averta'), local('Averta Bold'), 16 | url(${averta}) format('woff2'), 17 | url(${avertaBold}) format('woff'); 18 | } 19 | ` 20 | 21 | addDecorator((storyFn) => ( 22 | 23 | 24 | 25 | 26 | {storyFn()} 27 | 28 | 29 | 30 | )) 31 | -------------------------------------------------------------------------------- /src/components/layout/Paragraph/index.tsx: -------------------------------------------------------------------------------- 1 | import classNames from 'classnames/bind' 2 | import { MouseEventHandler, CSSProperties, ReactElement, ReactNode } from 'react' 3 | 4 | import styles from './index.module.scss' 5 | 6 | const cx = classNames.bind(styles) 7 | 8 | interface Props { 9 | align?: string 10 | children: ReactNode 11 | className?: string 12 | color?: string 13 | dot?: string 14 | noMargin?: boolean 15 | size?: string 16 | transform?: string 17 | weight?: string 18 | onClick?: MouseEventHandler 19 | style?: CSSProperties 20 | title?: string 21 | } 22 | 23 | const Paragraph = (props: Props): ReactElement => { 24 | const { align, children, className, color, dot, noMargin, size, transform, weight, ...restProps } = props 25 | return ( 26 |

30 | {children} 31 |

32 | ) 33 | } 34 | 35 | export default Paragraph 36 | -------------------------------------------------------------------------------- /src/logic/hooks/useMnemonicName.ts: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react' 2 | 3 | import { getChainName } from 'src/config' 4 | import { animalsDict, adjectivesDict } from './useMnemonicName.dict' 5 | 6 | const animals: string[] = animalsDict.trim().split(/\s+/) 7 | const adjectives: string[] = adjectivesDict.trim().split(/\s+/) 8 | 9 | const getRandomItem = (arr: T[]): T => { 10 | return arr[Math.floor(arr.length * Math.random())] 11 | } 12 | 13 | export const getRandomName = (noun = getRandomItem(animals)): string => { 14 | const adj = getRandomItem(adjectives) 15 | return `${adj}-${noun}` 16 | } 17 | 18 | export const useMnemonicName = (noun?: string): string => { 19 | const [name, setName] = useState('') 20 | 21 | useEffect(() => { 22 | setName(getRandomName(noun)) 23 | }, [noun]) 24 | 25 | return name 26 | } 27 | 28 | export const useMnemonicSafeName = (): string => { 29 | const networkName = getChainName().toLowerCase() 30 | return useMnemonicName(`${networkName}-safe`) 31 | } 32 | -------------------------------------------------------------------------------- /src/logic/safe/utils/shouldSafeStoreBeUpdated.ts: -------------------------------------------------------------------------------- 1 | import isEqual from 'lodash/isEqual' 2 | 3 | import { SafeRecordProps } from 'src/logic/safe/store/models/safe' 4 | 5 | // This function checks if an object is a Subset of a Safe State and that they have the same values 6 | const isStateSubset = (superObj, subObj) => { 7 | return Object.keys(subObj).every((key) => { 8 | if (subObj[key] && typeof subObj[key] == 'object') { 9 | if (typeof subObj[key] === 'object' || subObj[key].size >= 0) { 10 | // If type is Immutable Map, List or Object we use Immutable equals 11 | return isEqual(superObj[key], subObj[key]) 12 | } 13 | return isStateSubset(superObj[key], subObj[key]) 14 | } 15 | return subObj[key] === superObj[key] 16 | }) 17 | } 18 | 19 | export const shouldSafeStoreBeUpdated = ( 20 | newSafeProps: Partial, 21 | oldSafeProps?: SafeRecordProps, 22 | ): boolean => { 23 | if (!oldSafeProps) return true 24 | 25 | return !isStateSubset(oldSafeProps, newSafeProps) 26 | } 27 | --------------------------------------------------------------------------------