├── .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 |
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 |
4 |
--------------------------------------------------------------------------------
/src/routes/safe/components/Transactions/TxList/assets/incoming.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/routes/safe/components/Transactions/TxList/assets/outgoing.svg:
--------------------------------------------------------------------------------
1 |
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 |
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 |
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 |
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 |
4 |
--------------------------------------------------------------------------------
/src/components/AppLayout/Header/assets/key.svg:
--------------------------------------------------------------------------------
1 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
18 | )
19 |
20 | export default GnoButtonLink
21 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/feature-request.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Feature request
3 | about: Create a feature request for the Gnosis Safe
4 | ---
5 |
6 |
17 |
18 | ## Overview
19 |
20 | ## Requirements
21 |
22 | ## Designs
23 |
24 | ## Links
25 |
--------------------------------------------------------------------------------
/src/logic/hooks/useDarkMode.ts:
--------------------------------------------------------------------------------
1 | import { useEffect } from 'react'
2 | import useCachedState from 'src/utils/storage/useCachedState'
3 |
4 | const useDarkMode = (): [boolean, (mode: boolean) => void] => {
5 | const [darkMode = false, setDarkMode] = useCachedState('darkMode')
6 |
7 | const toggleDarkMode = (toggle: boolean): void => {
8 | document.documentElement.className = toggle ? 'darkMode' : ''
9 | }
10 |
11 | useEffect(() => {
12 | toggleDarkMode(darkMode)
13 | }, [darkMode])
14 |
15 | return [darkMode, setDarkMode]
16 | }
17 |
18 | export default useDarkMode
19 |
--------------------------------------------------------------------------------
/src/logic/safe/store/actions/loadSafesFromStorage.ts:
--------------------------------------------------------------------------------
1 | import { Dispatch } from 'redux'
2 | import { getLocalSafes } from 'src/logic/safe/utils'
3 | import { buildSafe } from 'src/logic/safe/store/reducer/safe'
4 | import { addOrUpdateSafe } from './addOrUpdateSafe'
5 |
6 | const loadSafesFromStorage =
7 | () =>
8 | (dispatch: Dispatch): void => {
9 | const safes = getLocalSafes()
10 |
11 | if (safes) {
12 | safes.forEach((safeProps) => {
13 | dispatch(addOrUpdateSafe(buildSafe(safeProps)))
14 | })
15 | }
16 | }
17 |
18 | export default loadSafesFromStorage
19 |
--------------------------------------------------------------------------------
/src/components/AppLayout/Header/components/ProviderDetails/MobilePairing.tsx:
--------------------------------------------------------------------------------
1 | import { lazy, ReactElement } from 'react'
2 | import { isPairingSupported } from 'src/logic/wallets/pairing/utils'
3 | import { wrapInSuspense } from 'src/utils/wrapInSuspense'
4 |
5 | const PairingDetails = lazy(() => import('src/components/AppLayout/Header/components/ProviderDetails/PairingDetails'))
6 |
7 | const MobilePairing = (props: { vertical?: boolean }): ReactElement | null => {
8 | return isPairingSupported() ? wrapInSuspense() : null
9 | }
10 |
11 | export default MobilePairing
12 |
--------------------------------------------------------------------------------
/src/logic/tokens/utils/humanReadableValue.ts:
--------------------------------------------------------------------------------
1 | import { BigNumber } from 'bignumber.js'
2 |
3 | export const humanReadableValue = (value: number | string, decimals = 18): string => {
4 | return new BigNumber(value).times(`1e-${decimals}`).toFixed()
5 | }
6 |
7 | export const fromTokenUnit = (amount: number | string, decimals: string | number): string =>
8 | new BigNumber(amount).times(`1e-${decimals}`).toFixed()
9 |
10 | export const toTokenUnit = (amount: number | string, decimals: string | number): string =>
11 | new BigNumber(amount).times(`1e${decimals}`).toFixed(0, BigNumber.ROUND_DOWN)
12 |
--------------------------------------------------------------------------------
/src/routes/safe/components/Apps/components/SecurityFeedbackModal/styles.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 | import { Title, Text } from '@gnosis.pm/safe-react-components'
3 |
4 | export const StyledTitle = styled(Title)<{ color?: string; bold?: boolean; centered?: boolean }>`
5 | text-align: ${({ centered }) => (centered ? 'center' : 'left')};
6 | margin: 24px 0;
7 | color: inherit;
8 | font-weight: ${({ bold }) => (bold ? 'bold' : 'normal')};
9 | `
10 |
11 | export const StyledSecurityTitle = styled(Text)`
12 | text-align: center;
13 | color: #b2bbc0;
14 | margin: 0 75px;
15 | `
16 |
--------------------------------------------------------------------------------
/src/utils/events/wallet.ts:
--------------------------------------------------------------------------------
1 | import { GTM_EVENT } from 'src/utils/googleTagManager'
2 | import { addEventCategory } from 'src/utils/events/utils'
3 |
4 | const WALLET = {
5 | CONNECT: {
6 | event: GTM_EVENT.META,
7 | action: 'Connect wallet',
8 | },
9 | OFF_CHAIN_SIGNATURE: {
10 | event: GTM_EVENT.META,
11 | action: 'Off-chain signature',
12 | },
13 | ON_CHAIN_INTERACTION: {
14 | event: GTM_EVENT.META,
15 | action: 'On-chain interaction',
16 | },
17 | }
18 |
19 | const WALLET_CATEGORY = 'wallet'
20 | export const WALLET_EVENTS = addEventCategory(WALLET, WALLET_CATEGORY)
21 |
--------------------------------------------------------------------------------
/scripts/cypress.js:
--------------------------------------------------------------------------------
1 | // eslint-disable-next-line @typescript-eslint/no-var-requires
2 | const { exec } = require('child_process')
3 |
4 | const DEPLOYMENTS = {
5 | dev: 'https://safe-team.dev.gnosisdev.com/app',
6 | staging: 'https://safe-team.staging.gnosisdev.com/app',
7 | prod: 'https://gnosis-safe.io/app',
8 | }
9 |
10 | const command = `cypress ${process.argv[2]}`
11 | // To accept 'prod' or 'production' as an argument
12 | let env = process.argv?.[3] === 'production' ? 'prod' : process.argv?.[3]
13 |
14 | exec(env ? `${command} --config baseUrl=${DEPLOYMENTS[env]} --env CYPRESS_ENV=${env}` : command)
15 |
--------------------------------------------------------------------------------
/scripts/github/prepare_production_deployment.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | set -ev
4 |
5 | # Only:
6 | # - Tagged commits
7 | # - Security env variables are available.
8 | if [ -n "$VERSION_TAG" ] && [ -n "$PROD_DEPLOYMENT_HOOK_TOKEN" ] && [ -n "$PROD_DEPLOYMENT_HOOK_URL" ]
9 | then
10 | curl --silent --output /dev/null --write-out "%{http_code}" -X POST \
11 | -F token="$PROD_DEPLOYMENT_HOOK_TOKEN" \
12 | -F ref=master \
13 | -F "variables[TRIGGER_RELEASE_COMMIT_TAG]=$VERSION_TAG" \
14 | $PROD_DEPLOYMENT_HOOK_URL
15 | else
16 | echo "[ERROR] Production deployment could not be prepared"
17 | fi
18 |
--------------------------------------------------------------------------------
/src/config/utils.ts:
--------------------------------------------------------------------------------
1 | import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses'
2 |
3 | // If this is in the index.ts file, it causes a circular dependency
4 | // CGW does not return `nativeCurrency.address` as it is `ZERO_ADDRESS`
5 | export const getNativeCurrencyAddress = (): string => {
6 | return ZERO_ADDRESS
7 | }
8 |
9 | // Template syntax returned from CGW is {{this}}
10 | export const evalTemplate = (uri: string, data: string | Record): string => {
11 | const TEMPLATE_REGEX = /\{\{([^}]+)\}\}/g
12 | return uri.replace(TEMPLATE_REGEX, (_: string, key: string) => data[key])
13 | }
14 |
--------------------------------------------------------------------------------
/src/logic/safe/store/actions/fetchLatestMasterContractVersion.ts:
--------------------------------------------------------------------------------
1 | import { Dispatch } from 'redux'
2 | import { getCurrentMasterContractLastVersion } from 'src/logic/safe/utils/safeVersion'
3 | import setLatestMasterContractVersion from 'src/logic/safe/store/actions/setLatestMasterContractVersion'
4 |
5 | const fetchLatestMasterContractVersion =
6 | () =>
7 | async (dispatch: Dispatch): Promise => {
8 | const latestVersion = await getCurrentMasterContractLastVersion()
9 |
10 | dispatch(setLatestMasterContractVersion(latestVersion))
11 | }
12 |
13 | export default fetchLatestMasterContractVersion
14 |
--------------------------------------------------------------------------------
/src/routes/safe/components/Transactions/TxList/Filter/validation.ts:
--------------------------------------------------------------------------------
1 | import { FilterForm } from '.'
2 |
3 | export const isValidAmount = (value: FilterForm['value']): string | undefined => {
4 | if (value && isNaN(Number(value))) {
5 | return 'Invalid number'
6 | }
7 | }
8 |
9 | export const isValidNonce = (value: FilterForm['nonce']): string | undefined => {
10 | if (value.length === 0) {
11 | return
12 | }
13 |
14 | const number = Number(value)
15 | if (isNaN(number)) {
16 | return 'Invalid number'
17 | }
18 | if (number < 0) {
19 | return 'Nonce cannot be negative'
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/layout/Button/index.tsx:
--------------------------------------------------------------------------------
1 | import Button from '@material-ui/core/Button'
2 | import { ReactElement } from 'react'
3 |
4 | const calculateStyleBased = (minWidth, minHeight) => ({
5 | minWidth: minWidth && `${minWidth}px`,
6 | minHeight: minHeight && `${minHeight}px`,
7 | })
8 |
9 | const GnoButton = ({ minWidth, minHeight = 35, testId = '', style = {}, ...props }: any): ReactElement => {
10 | const calculatedStyle = calculateStyleBased(minWidth, minHeight)
11 |
12 | return
13 | }
14 |
15 | export default GnoButton
16 |
--------------------------------------------------------------------------------
/src/components/layout/Backdrop/index.tsx:
--------------------------------------------------------------------------------
1 | import Backdrop from '@material-ui/core/Backdrop'
2 | import { makeStyles } from '@material-ui/core/styles'
3 | import { ReactElement } from 'react'
4 | import ReactDOM from 'react-dom'
5 |
6 | const useStyles = makeStyles({
7 | root: {
8 | zIndex: 0,
9 | top: '52px',
10 | },
11 | })
12 |
13 | const BackdropLayout = ({ isOpen }: { isOpen: boolean }): ReactElement | null => {
14 | const classes = useStyles()
15 |
16 | return ReactDOM.createPortal(, document.body)
17 | }
18 |
19 | export default BackdropLayout
20 |
--------------------------------------------------------------------------------
/src/logic/safe/store/selectors/utils.ts:
--------------------------------------------------------------------------------
1 | import hash from 'object-hash'
2 | import isEqual from 'lodash/isEqual'
3 | import memoize from 'lodash/memoize'
4 | import { createSelectorCreator, defaultMemoize } from 'reselect'
5 |
6 | import { AppReduxState } from 'src/store'
7 |
8 | export const createIsEqualSelector = createSelectorCreator(defaultMemoize, isEqual)
9 |
10 | const hashFn = (gatewayTransactions: AppReduxState['gatewayTransactions'], safeAddress: string): string =>
11 | hash(gatewayTransactions[safeAddress] ?? {})
12 |
13 | export const createHashBasedSelector = createSelectorCreator(memoize as any, hashFn)
14 |
--------------------------------------------------------------------------------
/src/components/AppLayout/Header/components/WalletIcon/icons/icon-fortmatic.svg:
--------------------------------------------------------------------------------
1 |
12 |
--------------------------------------------------------------------------------
/src/components/List/ListIcon.tsx:
--------------------------------------------------------------------------------
1 | import styled from 'styled-components'
2 | import { Icon, IconTypes } from '@gnosis.pm/safe-react-components'
3 | import { ThemeColors, ThemeIconSize } from '@gnosis.pm/safe-react-components/dist/theme'
4 |
5 | const StyledIcon = styled(Icon)`
6 | margin-right: 14px;
7 | `
8 |
9 | type Props = {
10 | type: IconTypes
11 | size?: ThemeIconSize
12 | color?: ThemeColors
13 | }
14 |
15 | const ListItemIcon = ({ type, size, color }: Props): React.ReactElement => (
16 |
17 | )
18 |
19 | export default ListItemIcon
20 |
--------------------------------------------------------------------------------
/src/logic/currencyValues/store/selectors/index.ts:
--------------------------------------------------------------------------------
1 | import { AppReduxState } from 'src/store'
2 | import { CURRENCY_REDUCER_ID, CurrencyValuesState } from 'src/logic/currencyValues/store/reducer/currencyValues'
3 |
4 | export const currencyValuesSelector = (state: AppReduxState): CurrencyValuesState => state[CURRENCY_REDUCER_ID]
5 |
6 | export const currentCurrencySelector = (state: AppReduxState): string => {
7 | return state[CURRENCY_REDUCER_ID].selectedCurrency
8 | }
9 |
10 | export const availableCurrenciesSelector = (state: AppReduxState): string[] => {
11 | return state[CURRENCY_REDUCER_ID].availableCurrencies
12 | }
13 |
--------------------------------------------------------------------------------
/src/logic/safe/utils/guardManager.ts:
--------------------------------------------------------------------------------
1 | import { getGnosisSafeInstanceAt } from 'src/logic/contracts/safeContracts'
2 | import { ZERO_ADDRESS } from 'src/logic/wallets/ethAddresses'
3 |
4 | export const getSetGuardTxData = (guardAddress: string, safeAddress: string, safeVersion: string): string => {
5 | const safeInstance = getGnosisSafeInstanceAt(safeAddress, safeVersion)
6 |
7 | return safeInstance.methods.setGuard(guardAddress).encodeABI()
8 | }
9 |
10 | export const getRemoveGuardTxData = (safeAddress: string, safeVersion: string): string => {
11 | return getSetGuardTxData(ZERO_ADDRESS, safeAddress, safeVersion)
12 | }
13 |
--------------------------------------------------------------------------------
/src/routes/safe/components/AddressBook/CreateEditEntryModal/style.ts:
--------------------------------------------------------------------------------
1 | import { createStyles, makeStyles } from '@material-ui/core/styles'
2 |
3 | import { lg, md } from 'src/theme/variables'
4 |
5 | export const useStyles = makeStyles(
6 | createStyles({
7 | heading: {
8 | padding: lg,
9 | justifyContent: 'space-between',
10 | boxSizing: 'border-box',
11 | height: '74px',
12 | },
13 | manage: {
14 | fontSize: lg,
15 | },
16 | container: {
17 | padding: `${md} ${lg}`,
18 | },
19 | close: {
20 | height: '35px',
21 | width: '35px',
22 | },
23 | }),
24 | )
25 |
--------------------------------------------------------------------------------
/src/logic/hooks/useStateHandler.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useCallback } from 'react'
2 |
3 | export type ReturnValue = {
4 | open: boolean
5 | toggle: (e?: Event) => void
6 | clickAway: () => void
7 | }
8 |
9 | export const useStateHandler = (openInitialValue = false): ReturnValue => {
10 | const [open, setOpen] = useState(openInitialValue)
11 | const toggle = useCallback((e?: Event) => {
12 | e?.stopPropagation()
13 |
14 | setOpen((prevOpen) => !prevOpen)
15 | }, [])
16 | const clickAway = useCallback(() => setOpen(false), [])
17 |
18 | return {
19 | open,
20 | toggle,
21 | clickAway,
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/logic/safe/store/reducer/types/safe.ts:
--------------------------------------------------------------------------------
1 | import { SafeRecord, SafeRecordProps } from 'src/logic/safe/store/models/safe'
2 | import { Map } from 'immutable'
3 |
4 | export type SafesMap = Map
5 |
6 | interface SafeReducerState {
7 | safes: SafesMap
8 | latestMasterContractVersion: string
9 | }
10 |
11 | interface SafeReducerStateJSON {
12 | safes: Record
13 | latestMasterContractVersion: string
14 | }
15 |
16 | export interface SafeReducerMap extends Map {
17 | toJS(): SafeReducerStateJSON
18 | get(key: K): SafeReducerState[K]
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/CustomIconText/index.tsx:
--------------------------------------------------------------------------------
1 | import { ReactElement } from 'react'
2 | import PrefixedEthHashInfo from '../PrefixedEthHashInfo'
3 |
4 | type Props = {
5 | address: string
6 | iconUrl?: string
7 | iconUrlFallback?: string
8 | text?: string
9 | }
10 |
11 | export const CustomIconText = ({ address, iconUrl, text, iconUrlFallback }: Props): ReactElement => (
12 |
22 | )
23 |
--------------------------------------------------------------------------------
/src/logic/addressBook/model/addressBook.ts:
--------------------------------------------------------------------------------
1 | import { ChainId } from 'src/config/chain.d'
2 |
3 | export const ADDRESS_BOOK_DEFAULT_NAME = 'UNKNOWN'
4 |
5 | export type AddressBookEntry = {
6 | address: string // the contact address
7 | name: string // human-readable name
8 | chainId: ChainId // see https://chainid.network
9 | }
10 |
11 | export const makeAddressBookEntry = ({
12 | address,
13 | name,
14 | chainId,
15 | }: {
16 | address: string
17 | name: string
18 | chainId: ChainId
19 | }): AddressBookEntry => ({
20 | address,
21 | name,
22 | chainId,
23 | })
24 |
25 | export type AddressBookState = AddressBookEntry[]
26 |
--------------------------------------------------------------------------------
/src/logic/safe/utils/safeInformation.ts:
--------------------------------------------------------------------------------
1 | import { getSafeInfo as fetchSafeInfo, SafeInfo } from '@gnosis.pm/safe-react-gateway-sdk'
2 |
3 | import { Errors, CodedException } from 'src/logic/exceptions/CodedException'
4 | import { _getChainId } from 'src/config'
5 |
6 | const GATEWAY_ERROR = /1337|42/
7 |
8 | export const getSafeInfo = async (safeAddress: string): Promise => {
9 | try {
10 | return await fetchSafeInfo(_getChainId(), safeAddress)
11 | } catch (e) {
12 | const safeNotFound = GATEWAY_ERROR.test(e.message)
13 | throw new CodedException(safeNotFound ? Errors._605 : Errors._613, e.message)
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/routes/safe/components/Settings/assets/icons/bin.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/routes/safe/components/Transactions/TxList/TxHoverProvider.tsx:
--------------------------------------------------------------------------------
1 | import { createContext, ReactElement, ReactNode, useState } from 'react'
2 |
3 | export const TxHoverContext = createContext<{
4 | activeHover?: string
5 | setActiveHover: (activeHover?: string) => void
6 | }>({
7 | activeHover: undefined,
8 | setActiveHover: () => {},
9 | })
10 |
11 | export const TxHoverProvider = ({ children }: { children: ReactNode }): ReactElement => {
12 | const [activeHover, setActiveHover] = useState()
13 |
14 | return {children}
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/WhenFieldChanges/index.tsx:
--------------------------------------------------------------------------------
1 | import { OnChange } from 'react-final-form-listeners'
2 |
3 | import GnoField from 'src/components/forms/Field'
4 |
5 | const WhenFieldChanges = ({ field, set, to }: { field: string; set: string; to: string }): React.ReactElement => (
6 |
7 | {(
8 | // No subscription. We only use Field to get to the change function
9 | { input: { onChange } },
10 | ) => (
11 |
12 | {() => {
13 | onChange(to)
14 | }}
15 |
16 | )}
17 |
18 | )
19 | export default WhenFieldChanges
20 |
--------------------------------------------------------------------------------
/src/logic/currentSession/hooks/useSafeAddress.ts:
--------------------------------------------------------------------------------
1 | import { useSelector } from 'react-redux'
2 | import { getShortName } from 'src/config'
3 | import { currentSafe } from 'src/logic/safe/store/selectors'
4 | import { currentSession } from '../store/selectors'
5 |
6 | const useSafeAddress = (): { shortName: string; safeAddress: string } => {
7 | const safe = useSelector(currentSafe)
8 | const { currentShortName, currentSafeAddress } = useSelector(currentSession)
9 |
10 | return {
11 | shortName: currentShortName || getShortName(),
12 | safeAddress: currentSafeAddress || safe.address,
13 | }
14 | }
15 |
16 | export default useSafeAddress
17 |
--------------------------------------------------------------------------------
/src/assets/icons/info.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/routes/safe/components/Settings/assets/icons/disabled-bin.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/src/logic/appearance/selectors/index.ts:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect'
2 |
3 | import { AppReduxState } from 'src/store'
4 | import { AppearanceState, APPEARANCE_REDUCER_ID } from '../reducer/appearance'
5 |
6 | const appearanceStateSelector = (state: AppReduxState): AppearanceState => state[APPEARANCE_REDUCER_ID]
7 |
8 | export const copyShortNameSelector = createSelector(
9 | appearanceStateSelector,
10 | ({ copyShortName }: AppearanceState): boolean => copyShortName,
11 | )
12 |
13 | export const showShortNameSelector = createSelector(
14 | appearanceStateSelector,
15 | ({ showShortName }: AppearanceState): boolean => showShortName,
16 | )
17 |
--------------------------------------------------------------------------------
/src/logic/safe/utils/mocks/mockTokenCurrenciesBalancesResponse.ts:
--------------------------------------------------------------------------------
1 | export const mockTokenCurrenciesBalancesResponse = {
2 | fiatTotal: '20425.465697000127',
3 | items: [
4 | {
5 | tokenInfo: {
6 | type: 'NATIVE_TOKEN',
7 | address: '0x0000000000000000000000000000000000000000',
8 | decimals: 18,
9 | symbol: 'ETH',
10 | name: 'Ether',
11 | logoUri: 'https://safe-transaction-assets.staging.gnosisdev.com/chains/4/currency_logo.png',
12 | },
13 | balance: '7000000000000000000',
14 | fiatBalance: '20425.465697000127',
15 | fiatConversion: '2917.923671000018',
16 | },
17 | ],
18 | }
19 |
--------------------------------------------------------------------------------
/src/routes/safe/components/Settings/ManageOwners/EditOwnerModal/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 | heading: {
8 | padding: lg,
9 | justifyContent: 'space-between',
10 | boxSizing: 'border-box',
11 | height: '74px',
12 | },
13 | manage: {
14 | fontSize: lg,
15 | },
16 | container: {
17 | padding: `${md} ${lg}`,
18 | minHeight: '200px',
19 | },
20 | close: {
21 | height: '35px',
22 | width: '35px',
23 | },
24 | }),
25 | )
26 |
--------------------------------------------------------------------------------
/src/assets/icons/alert.svg:
--------------------------------------------------------------------------------
1 |
6 |
--------------------------------------------------------------------------------
/src/utils/prefixedAddress.ts:
--------------------------------------------------------------------------------
1 | import { getShortName } from 'src/config'
2 | import { getChains } from 'src/config/cache/chains'
3 | import { ShortName } from 'src/config/chain.d'
4 |
5 | export const isValidPrefix = (prefix: ShortName): boolean => {
6 | return getChains().some(({ shortName }) => shortName === prefix)
7 | }
8 |
9 | export const parsePrefixedAddress = (fullAddress = ''): { address: string; prefix: ShortName } => {
10 | const parts = fullAddress.split(':').filter(Boolean)
11 | const address = parts.length > 1 ? parts[1] : parts[0] || ''
12 | const prefix = parts.length > 1 ? parts[0] || '' : getShortName()
13 | return { prefix, address }
14 | }
15 |
--------------------------------------------------------------------------------
/prod.Dockerfile:
--------------------------------------------------------------------------------
1 | FROM node:14 as react-build-step
2 |
3 | # Grab needed environment variables from .env.example
4 | ENV REACT_APP_ENV=production
5 |
6 | RUN apt-get update \
7 | && apt-get install -y libusb-1.0-0 libusb-1.0-0-dev libudev-dev \
8 | && rm -rf /var/lib/apt/lists/*
9 |
10 | WORKDIR /app
11 |
12 | COPY package.json yarn.lock .
13 |
14 | RUN yarn install
15 |
16 | COPY . .
17 |
18 | RUN yarn build
19 |
20 | # Deploy the build
21 | FROM nginx:1-alpine
22 |
23 | COPY ./docker/nginx.conf /etc/nginx/conf.d/default.conf
24 | COPY --from=react-build-step /app/build /usr/share/nginx/html/
25 |
26 | EXPOSE 80
27 |
28 | CMD ["nginx", "-g", "daemon off;"]
29 |
--------------------------------------------------------------------------------
/src/logic/hooks/useRecommendedNonce.tsx:
--------------------------------------------------------------------------------
1 | import { useSelector } from 'react-redux'
2 | import { getRecommendedNonce } from 'src/logic/safe/api/fetchSafeTxGasEstimation'
3 | import { getLastTxNonce } from 'src/logic/safe/store/selectors/gatewayTransactions'
4 | import useAsync from 'src/logic/hooks/useAsync'
5 |
6 | const useRecommendedNonce = (safeAddress: string): number => {
7 | const lastTxNonce = useSelector(getLastTxNonce) || -1
8 |
9 | const [result] = useAsync(() => {
10 | return getRecommendedNonce(safeAddress)
11 | }, [safeAddress])
12 |
13 | return result == null ? lastTxNonce + 1 : result
14 | }
15 |
16 | export default useRecommendedNonce
17 |
--------------------------------------------------------------------------------
/src/utils/css.ts:
--------------------------------------------------------------------------------
1 | export const upperFirst = (value: string): string => value.charAt(0).toUpperCase() + value.toLowerCase().slice(1)
2 |
3 | export const capitalize = (value: string, prefix?: string): undefined | boolean | string | number => {
4 | if (!value) {
5 | return undefined
6 | }
7 |
8 | if (typeof value === 'boolean') {
9 | return prefix || ''
10 | }
11 |
12 | if (typeof value === 'number') {
13 | return prefix ? `${prefix}${value}` : value
14 | }
15 |
16 | if (typeof value !== 'string') {
17 | return false
18 | }
19 |
20 | const capitalized = upperFirst(value)
21 |
22 | return prefix ? `${prefix}${capitalized}` : capitalized
23 | }
24 |
--------------------------------------------------------------------------------
/public/migrate.html:
--------------------------------------------------------------------------------
1 |
2 |
23 |
--------------------------------------------------------------------------------
/src/routes/safe/components/Settings/ManageOwners/RemoveOwnerModal/screens/ThresholdForm/style.ts:
--------------------------------------------------------------------------------
1 | import { createStyles, makeStyles } from '@material-ui/core/styles'
2 |
3 | import { lg, md, sm } from 'src/theme/variables'
4 |
5 | export const useStyles = makeStyles(
6 | createStyles({
7 | headingText: {
8 | fontSize: md,
9 | },
10 | formContainer: {
11 | padding: `${md} ${lg}`,
12 | minHeight: '340px',
13 | },
14 | ownersText: {
15 | marginLeft: sm,
16 | },
17 | inputRow: {
18 | position: 'relative',
19 | },
20 | errorText: {
21 | position: 'absolute',
22 | bottom: '-25px',
23 | },
24 | }),
25 | )
26 |
--------------------------------------------------------------------------------
/src/routes/safe/components/Transactions/TxList/InfoDetails.tsx:
--------------------------------------------------------------------------------
1 | import { ReactElement, ReactNode } from 'react'
2 | import { Text } from '@gnosis.pm/safe-react-components'
3 |
4 | import { StyledTxInfoDetails } from 'src/routes/safe/components/Transactions/TxList/styled'
5 |
6 | type InfoDetailsProps = {
7 | header?: string | ReactElement
8 | children: ReactNode
9 | title: string | ReactElement
10 | }
11 |
12 | export const InfoDetails = ({ header, children, title }: InfoDetailsProps): ReactElement => (
13 |
14 | {header}
15 |
16 | {title}
17 |
18 | {children}
19 |
20 | )
21 |
--------------------------------------------------------------------------------
/src/utils/storage/__tests__/index.test.ts:
--------------------------------------------------------------------------------
1 | import { getStoragePrefix } from '..'
2 |
3 | describe('getStoragePrefix', () => {
4 | it('saves a stringified value', () => {
5 | expect(getStoragePrefix('137')).toBe('_immortal|v2_POLYGON__')
6 | expect(getStoragePrefix('4')).toBe('_immortal|v2_RINKEBY__')
7 | expect(getStoragePrefix('1')).toBe('_immortal|v2_MAINNET__')
8 | expect(getStoragePrefix('100')).toBe('_immortal|v2_XDAI__')
9 | expect(getStoragePrefix('246')).toBe('_immortal|v2_ENERGY_WEB_CHAIN__')
10 | expect(getStoragePrefix('9358392457')).toBe('_immortal|v2_9358392457__')
11 | expect(getStoragePrefix()).toBe('_immortal|v2_RINKEBY__')
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/src/components/layout/Row/index.module.scss:
--------------------------------------------------------------------------------
1 | @import "src/theme/variables.scss";
2 |
3 | .row {
4 | display: flex;
5 | flex: 0 1 auto;
6 | flex-direction: row;
7 | flex-wrap: wrap;
8 | }
9 | .grow {
10 | flex: 1 1 auto;
11 | }
12 |
13 | .marginXs {
14 | margin-bottom: $xs;
15 | }
16 |
17 | .marginSm {
18 | margin-bottom: $sm;
19 | }
20 |
21 | .marginMd {
22 | margin-bottom: $md;
23 | }
24 |
25 | .marginLg {
26 | margin-bottom: $lg;
27 | }
28 |
29 | .marginXl {
30 | margin-bottom: $xl;
31 | }
32 |
33 | .alignStart {
34 | align-items: flex-start;
35 | }
36 |
37 | .alignEnd {
38 | align-items: flex-end;
39 | }
40 |
41 | .alignCenter {
42 | align-items: center;
43 | }
--------------------------------------------------------------------------------
/src/logic/wallets/errors.ts:
--------------------------------------------------------------------------------
1 | const isKeystoneError = (err: unknown): boolean => {
2 | if (err instanceof Error) {
3 | return err.message?.startsWith('#ktek_error')
4 | }
5 | return false
6 | }
7 |
8 | const isWCRejection = (err: Error): boolean => {
9 | return /User rejected/.test(err?.message)
10 | }
11 |
12 | const isMMRejection = (err: Error & { code?: number }): boolean => {
13 | const METAMASK_REJECT_CONFIRM_TX_ERROR_CODE = 4001
14 |
15 | return err.code === METAMASK_REJECT_CONFIRM_TX_ERROR_CODE
16 | }
17 |
18 | export const isWalletRejection = (err: Error & { code?: number }): boolean => {
19 | return isMMRejection(err) || isWCRejection(err) || isKeystoneError(err)
20 | }
21 |
--------------------------------------------------------------------------------
/src/utils/history.ts:
--------------------------------------------------------------------------------
1 | import { _getChainId } from 'src/config'
2 | import { getChains } from 'src/config/cache/chains'
3 | import { setChainId } from 'src/logic/config/utils'
4 | import { extractPrefixedSafeAddress } from 'src/routes/routes'
5 |
6 | export const setChainIdFromUrl = (pathname: string): boolean => {
7 | const { shortName, safeAddress } = extractPrefixedSafeAddress(pathname)
8 |
9 | if (!safeAddress) return false
10 |
11 | const chainId = getChains().find((chain) => chain.shortName === shortName)?.chainId
12 |
13 | if (chainId) {
14 | if (chainId !== _getChainId()) {
15 | setChainId(chainId)
16 | }
17 | return true
18 | }
19 |
20 | return false
21 | }
22 |
--------------------------------------------------------------------------------
/src/config/__tests__/index.test.ts:
--------------------------------------------------------------------------------
1 | import { setChainId } from 'src/logic/config/utils'
2 | import { CHAIN_ID } from '../chain.d'
3 | import { getPublicRpcUrl } from '..'
4 |
5 | describe('Config getters', () => {
6 | describe('getPublicRpcUrl', () => {
7 | afterEach(() => {
8 | setChainId(CHAIN_ID.RINKEBY)
9 | })
10 |
11 | it('gets a public RPC from the config', () => {
12 | setChainId(CHAIN_ID.ETHEREUM)
13 | expect(getPublicRpcUrl()).toMatch('https://mainnet.infura.io/v3/')
14 | })
15 |
16 | it('returns nothing for a non-existent config', () => {
17 | setChainId(CHAIN_ID.UNKNOWN)
18 | expect(getPublicRpcUrl()).toBe('')
19 | })
20 | })
21 | })
22 |
--------------------------------------------------------------------------------
/src/logic/safe/store/models/types/transaction.ts:
--------------------------------------------------------------------------------
1 | import { Operation } from '@gnosis.pm/safe-react-gateway-sdk'
2 | import { GnosisSafe } from 'src/types/contracts/gnosis_safe.d'
3 |
4 | export enum PendingActionType {
5 | CONFIRM = 'confirm',
6 | REJECT = 'reject',
7 | }
8 | export type PendingActionValues = PendingActionType[keyof PendingActionType]
9 |
10 | export type TxArgs = {
11 | baseGas: string
12 | data: string
13 | gasPrice: string
14 | gasToken: string
15 | nonce: number
16 | operation: Operation
17 | refundReceiver: string
18 | safeInstance: GnosisSafe
19 | safeTxGas: string
20 | sender: string
21 | sigs: string
22 | to: string
23 | valueInWei: string
24 | }
25 |
--------------------------------------------------------------------------------
/src/routes/safe/components/Settings/ManageOwners/AddOwnerModal/screens/ThresholdForm/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 | headingText: {
6 | fontSize: md,
7 | },
8 | formContainer: {
9 | padding: `${md} ${lg}`,
10 | minHeight: '340px',
11 | },
12 | ownersText: {
13 | marginLeft: sm,
14 | },
15 | buttonRow: {
16 | height: '84px',
17 | justifyContent: 'center',
18 | gap: '16px',
19 | },
20 | inputRow: {
21 | position: 'relative',
22 | },
23 | errorText: {
24 | position: 'absolute',
25 | bottom: '-25px',
26 | },
27 | })
28 |
--------------------------------------------------------------------------------
/src/utils/checksumAddress.ts:
--------------------------------------------------------------------------------
1 | import { Errors, logError } from 'src/logic/exceptions/CodedException'
2 | import { checkAddressChecksum, toChecksumAddress } from 'web3-utils'
3 | import { isValidAddress } from './isValidAddress'
4 |
5 | export const checksumAddress = (address: string): string => {
6 | if (!isValidAddress(address)) {
7 | return ''
8 | }
9 |
10 | try {
11 | return toChecksumAddress(address)
12 | } catch (err) {
13 | logError(Errors._102, err.message)
14 | return ''
15 | }
16 | }
17 |
18 | export const isChecksumAddress = (address?: string): boolean => {
19 | if (address) {
20 | return checkAddressChecksum(address)
21 | }
22 |
23 | return false
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/layout/Heading/index.tsx:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames/bind'
2 | import * as React from 'react'
3 |
4 | import styles from './index.module.scss'
5 |
6 | import { capitalize } from 'src/utils/css'
7 |
8 | const cx = classNames.bind(styles)
9 |
10 | const Heading = (props) => {
11 | const { align, children, className = '', color, margin, tag, testId, truncate, ...rest } = props
12 |
13 | const classes = cx(className, 'heading', align, tag, margin ? capitalize(margin, 'margin') : undefined, color, {
14 | truncate,
15 | })
16 |
17 | return React.createElement(tag, { ...rest, className: classes, 'data-testid': testId || '' }, children)
18 | }
19 |
20 | export default Heading
21 |
--------------------------------------------------------------------------------
/src/components/layout/Img/index.tsx:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames/bind'
2 | import { ReactElement, ImgHTMLAttributes } from 'react'
3 |
4 | import styles from './index.module.scss'
5 |
6 | const cx = classNames.bind(styles)
7 |
8 | type ImgProps = ImgHTMLAttributes & {
9 | bordered?: boolean
10 | fullwidth?: boolean
11 | testId?: string
12 | }
13 |
14 | const Img = ({ alt, bordered, className, fullwidth, style, testId = '', ...props }: ImgProps): ReactElement => {
15 | const classes = cx(styles.img, { fullwidth, bordered }, className)
16 |
17 | return
18 | }
19 |
20 | export default Img
21 |
--------------------------------------------------------------------------------
/src/routes/safe/components/Transactions/TxList/TxModuleInfo.tsx:
--------------------------------------------------------------------------------
1 | import { ReactElement } from 'react'
2 | import { ModuleExecutionDetails } from '@gnosis.pm/safe-react-gateway-sdk'
3 |
4 | import { AddressInfo } from './AddressInfo'
5 | import { InfoDetails } from './InfoDetails'
6 |
7 | const TxModuleInfo = ({ detailedExecutionInfo }: { detailedExecutionInfo: ModuleExecutionDetails }): ReactElement => {
8 | const { value, name, logoUri } = detailedExecutionInfo.address
9 |
10 | return (
11 |
12 |
13 |
14 | )
15 | }
16 |
17 | export default TxModuleInfo
18 |
--------------------------------------------------------------------------------
/src/components/layout/Hairline/index.tsx:
--------------------------------------------------------------------------------
1 | import { ReactElement } from 'react'
2 |
3 | import { getSize } from 'src/theme/size'
4 | import { border } from 'src/theme/variables'
5 |
6 | const calculateStyleFrom = (color, margin) => ({
7 | width: '100%',
8 | minHeight: '2px',
9 | height: '2px',
10 | backgroundColor: color || border,
11 | margin: `${getSize(margin)} 0px`,
12 | })
13 |
14 | const Hairline = ({ className, color, margin, style }: any): ReactElement => {
15 | const calculatedStyles = calculateStyleFrom(color, margin)
16 | const mergedStyles = { ...calculatedStyles, ...(style || {}) }
17 |
18 | return
19 | }
20 |
21 | export default Hairline
22 |
--------------------------------------------------------------------------------
/src/components/layout/Block/index.tsx:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames/bind'
2 | import * as React from 'react'
3 |
4 | import { capitalize } from 'src/utils/css'
5 | import styles from './index.module.scss'
6 |
7 | const cx = classNames.bind(styles)
8 |
9 | class Block extends React.PureComponent {
10 | render(): React.ReactElement {
11 | const { children, className, justify, margin, padding, ...props } = this.props
12 |
13 | const paddingStyle = padding ? capitalize(padding, 'padding') : undefined
14 | return (
15 |
16 | {children}
17 |
18 | )
19 | }
20 | }
21 |
22 | export default Block
23 |
--------------------------------------------------------------------------------
/src/routes/safe/components/Settings/assets/icons/SafeDetailsIcon.tsx:
--------------------------------------------------------------------------------
1 | export const SafeDetailsIcon = (): React.ReactElement => (
2 |
14 | )
15 |
--------------------------------------------------------------------------------
/src/utils/storage/useCachedState.ts:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 | import local from './local'
3 | import session from './session'
4 |
5 | const useCachedState = (
6 | key: string,
7 | isSession = false,
8 | ): [T | undefined, React.Dispatch>] => {
9 | const [cache, setCache] = useState()
10 | const storage = isSession ? session : local
11 |
12 | useEffect(() => {
13 | const saved = storage.getItem(key)
14 | setCache(saved)
15 | }, [key, storage, setCache])
16 |
17 | useEffect(() => {
18 | storage.setItem(key, cache)
19 | }, [key, storage, cache])
20 |
21 | return [cache, setCache]
22 | }
23 |
24 | export default useCachedState
25 |
--------------------------------------------------------------------------------
/src/components/layout/Tooltip/index.tsx:
--------------------------------------------------------------------------------
1 | import { withStyles, Theme, Tooltip as MuiTooltip } from '@material-ui/core'
2 |
3 | // Tooltip doesn't accept a className, preventing use of `styled-components`
4 | export const Tooltip = withStyles(({ palette }: Theme) => ({
5 | arrow: {
6 | '&::before': {
7 | backgroundColor: palette.common.white,
8 | boxShadow: '1px 2px 10px rgba(40, 54, 61, 0.18)',
9 | },
10 | },
11 | tooltip: {
12 | color: palette.common.black,
13 | backgroundColor: palette.common.white,
14 | borderRadius: '8px',
15 | boxShadow: '1px 2px 10px rgba(40, 54, 61, 0.18)',
16 | fontSize: '14px',
17 | padding: '16px',
18 | lineHeight: 'normal',
19 | },
20 | }))(MuiTooltip)
21 |
--------------------------------------------------------------------------------
/src/routes/safe/components/Balances/SendModal/screens/SendFunds/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 | manage: {
6 | fontSize: lg,
7 | },
8 | qrCodeBtn: {
9 | cursor: 'pointer',
10 | },
11 | formContainer: {
12 | padding: `${md} ${lg}`,
13 | minHeight: '216px',
14 | },
15 | buttonRow: {
16 | height: '84px',
17 | justifyContent: 'center',
18 | gap: '16px',
19 | },
20 | selectAddress: {
21 | cursor: 'pointer',
22 | },
23 | warningRow: {
24 | display: 'flex',
25 | alignItems: 'center',
26 | },
27 | warningIcon: {
28 | marginRight: sm,
29 | },
30 | })
31 |
--------------------------------------------------------------------------------
/src/routes/safe/components/Settings/SpendingLimit/FormFields/Token.tsx:
--------------------------------------------------------------------------------
1 | import { ReactElement } from 'react'
2 | import { useSelector } from 'react-redux'
3 | import styled from 'styled-components'
4 |
5 | import TokenSelectField from 'src/routes/safe/components/Balances/SendModal/screens/SendFunds/TokenSelectField'
6 | import { extendedSafeTokensSelector } from 'src/routes/safe/container/selector'
7 |
8 | const TokenInput = styled.div`
9 | grid-area: tokenInput;
10 | `
11 |
12 | const Token = (): ReactElement => {
13 | const tokens = useSelector(extendedSafeTokensSelector)
14 |
15 | return (
16 |
17 |
18 |
19 | )
20 | }
21 |
22 | export default Token
23 |
--------------------------------------------------------------------------------
/src/logic/hooks/useWindowDimensions.tsx:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from 'react'
2 |
3 | function getWindowDimensions() {
4 | const { innerWidth: width, innerHeight: height } = window
5 | return {
6 | width,
7 | height,
8 | }
9 | }
10 |
11 | export const useWindowDimensions = (): { width: number; height: number } => {
12 | const [windowDimensions, setWindowDimensions] = useState(getWindowDimensions())
13 |
14 | useEffect(() => {
15 | function handleResize() {
16 | setWindowDimensions(getWindowDimensions())
17 | }
18 |
19 | window.addEventListener('resize', handleResize)
20 | return () => window.removeEventListener('resize', handleResize)
21 | }, [])
22 |
23 | return windowDimensions
24 | }
25 |
--------------------------------------------------------------------------------
/src/components/PsaBanner/index.module.scss:
--------------------------------------------------------------------------------
1 | .banner {
2 | position: fixed;
3 | z-index: 10000;
4 | top: 0;
5 | left: 0;
6 | right: 0;
7 | background-color: rgb(0, 140, 115);
8 | color: #fff;
9 | padding: 5px 20px;
10 | font-size: 16px;
11 | }
12 |
13 | .banner a {
14 | color: inherit;
15 | font-weight: bold;
16 | }
17 |
18 | .wrapper {
19 | position: relative;
20 | display: flex;
21 | flex-direction: column;
22 | alignitems: center;
23 | height: 70px;
24 | }
25 |
26 | .content {
27 | max-width: 960px;
28 | margin: 0 auto;
29 | text-align: center;
30 | padding: 10px;
31 | }
32 |
33 | .close {
34 | position: absolute;
35 | right: 10px;
36 | top: 10px;
37 | cursor: pointer;
38 | z-index: 2;
39 | }
40 |
--------------------------------------------------------------------------------
/src/logic/hooks/useSafeAppUrl.tsx:
--------------------------------------------------------------------------------
1 | import { useLocation } from 'react-router-dom'
2 | import { useCallback } from 'react'
3 | import { sanitizeUrl } from 'src/utils/sanitizeUrl'
4 |
5 | type AppUrlReturnType = {
6 | getAppUrl: () => string
7 | }
8 |
9 | export const useSafeAppUrl = (): AppUrlReturnType => {
10 | const { search } = useLocation()
11 |
12 | const getAppUrl = useCallback(() => {
13 | const query = new URLSearchParams(search)
14 | try {
15 | const url = query.get('appUrl')
16 |
17 | return sanitizeUrl(url)
18 | } catch {
19 | throw new Error('Detected javascript injection in the URL. Check the appUrl parameter')
20 | }
21 | }, [search])
22 |
23 | return {
24 | getAppUrl,
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/src/logic/safe/utils/mocks/getSafeMock.ts:
--------------------------------------------------------------------------------
1 | export const mockGetSafeInfoResponse = {
2 | chainId: '4',
3 | address: { value: '0x57CB13cbef735FbDD65f5f2866638c546464E45F' },
4 | nonce: 0,
5 | threshold: 1,
6 | owners: [
7 | { value: '0x680cde08860141F9D223cE4E620B10Cd6741037E' },
8 | { value: '0xfe8BEBd43Ac213bea4bb8eC9e2dd90632f9371b2' },
9 | ],
10 | implementation: { value: '0xd9Db270c1B5E3Bd161E8c8503c55cEABeE709552' },
11 | modules: null,
12 | fallbackHandler: { value: '0xf48f2B2d2a534e402487b3ee7C18c33Aec0Fe5e4' },
13 | guard: null,
14 | version: '1.3.0',
15 | implementationVersionState: 'UP_TO_DATE',
16 | collectiblesTag: '1629729817',
17 | txQueuedTag: '1629729817',
18 | txHistoryTag: '1629729817',
19 | }
20 |
--------------------------------------------------------------------------------
/src/assets/icons/check.svg:
--------------------------------------------------------------------------------
1 |
5 |
--------------------------------------------------------------------------------
/src/routes/safe/components/assets/BalancesIcon.tsx:
--------------------------------------------------------------------------------
1 | export const BalancesIcon = (): React.ReactElement => (
2 |
16 | )
17 |
--------------------------------------------------------------------------------
/src/assets/icons/apps.svg:
--------------------------------------------------------------------------------
1 |
13 |
14 |
--------------------------------------------------------------------------------
/src/logic/safe/store/actions/transactions/gatewayTransactions.ts:
--------------------------------------------------------------------------------
1 | import { createAction } from 'redux-actions'
2 |
3 | import { HistoryPayload, QueuedPayload, RemoveHistoryPayload } from 'src/logic/safe/store/reducer/gatewayTransactions'
4 |
5 | export const ADD_HISTORY_TRANSACTIONS = 'ADD_HISTORY_TRANSACTIONS'
6 | export const addHistoryTransactions = createAction(ADD_HISTORY_TRANSACTIONS)
7 |
8 | export const REMOVE_HISTORY_TRANSACTIONS = 'REMOVE_HISTORY_TRANSACTIONS'
9 | export const removeHistoryTransactions = createAction(REMOVE_HISTORY_TRANSACTIONS)
10 |
11 | export const ADD_QUEUED_TRANSACTIONS = 'ADD_QUEUED_TRANSACTIONS'
12 | export const addQueuedTransactions = createAction(ADD_QUEUED_TRANSACTIONS)
13 |
--------------------------------------------------------------------------------
/src/components/layout/Row/index.tsx:
--------------------------------------------------------------------------------
1 | import classNames from 'classnames/bind'
2 | import { ReactElement } from 'react'
3 |
4 | import styles from './index.module.scss'
5 |
6 | import { capitalize } from 'src/utils/css'
7 |
8 | const cx = classNames.bind(styles)
9 |
10 | const Row = ({ align, children, className, grow, margin, testId = '', ...props }: any): ReactElement => {
11 | const rowClassNames = cx(
12 | styles.row,
13 | margin ? capitalize(margin, 'margin') : undefined,
14 | align ? capitalize(align, 'align') : undefined,
15 | { grow },
16 | className,
17 | )
18 |
19 | return (
20 |
21 | {children}
22 |
23 | )
24 | }
25 |
26 | export default Row
27 |
--------------------------------------------------------------------------------
/src/logic/cookies/model/cookie.ts:
--------------------------------------------------------------------------------
1 | export const COOKIES_KEY = 'COOKIES'
2 | export const COOKIES_KEY_INTERCOM = `${COOKIES_KEY}_INTERCOM`
3 |
4 | export enum COOKIE_IDS {
5 | INTERCOM = 'INTERCOM',
6 | BEAMER = 'BEAMER',
7 | }
8 |
9 | export const COOKIE_ALERTS: Record = {
10 | INTERCOM: 'You attempted to open the customer support chat. Please accept the community support & updates cookies',
11 | BEAMER: "You attempted to open the What's New section. Please accept the community support & updates cookies.",
12 | }
13 |
14 | export type BannerCookiesType = {
15 | acceptedNecessary: boolean
16 | acceptedSupportAndUpdates: boolean
17 | acceptedAnalytics: boolean
18 | }
19 | export type IntercomCookieType = {
20 | userId: string
21 | }
22 |
--------------------------------------------------------------------------------
/src/logic/hooks/useIsSmartContractWallet.ts:
--------------------------------------------------------------------------------
1 | import { useEffect, useState } from 'react'
2 |
3 | import { isSmartContractWallet } from 'src/logic/wallets/getWeb3'
4 |
5 | const useIsSmartContractWallet = (userAccount: string): boolean => {
6 | const [isSmart, setIsSmart] = useState(false)
7 |
8 | useEffect(() => {
9 | let isCurrent = true
10 |
11 | const checkAddress = async () => {
12 | // isSmartContractWallet is memoized
13 | const res = await isSmartContractWallet(userAccount)
14 | if (isCurrent) setIsSmart(res)
15 | }
16 | checkAddress()
17 |
18 | return () => {
19 | isCurrent = false
20 | }
21 | }, [userAccount])
22 |
23 | return isSmart
24 | }
25 |
26 | export default useIsSmartContractWallet
27 |
--------------------------------------------------------------------------------
/src/routes/safe/components/Settings/ThresholdSettings/style.ts:
--------------------------------------------------------------------------------
1 | import { createStyles } from '@material-ui/core/styles'
2 | import { border, fontColor, lg, secondaryText, smallFontSize, xl } from 'src/theme/variables'
3 |
4 | export const styles = createStyles({
5 | ownersText: {
6 | color: secondaryText,
7 | '& b': {
8 | color: fontColor,
9 | },
10 | },
11 | container: {
12 | padding: lg,
13 | },
14 | buttonRow: {
15 | padding: lg,
16 | position: 'absolute',
17 | left: 0,
18 | bottom: 0,
19 | boxSizing: 'border-box',
20 | width: '100%',
21 | justifyContent: 'flex-end',
22 | borderTop: `2px solid ${border}`,
23 | },
24 | modifyBtn: {
25 | height: xl,
26 | fontSize: smallFontSize,
27 | },
28 | })
29 |
--------------------------------------------------------------------------------
/src/logic/currentSession/store/selectors/index.ts:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect'
2 |
3 | import { CURRENT_SESSION_REDUCER_ID, CurrentSessionState } from 'src/logic/currentSession/store/reducer/currentSession'
4 | import { AppReduxState } from 'src/store'
5 |
6 | export const lastViewedSafe = (state: AppReduxState['currentSession']): string | null => {
7 | const currentSession = state[CURRENT_SESSION_REDUCER_ID]
8 | if (!currentSession.restored) {
9 | return null
10 | }
11 | return currentSession.viewedSafes[0] || ''
12 | }
13 |
14 | export const currentSession = (state: AppReduxState): CurrentSessionState => state[CURRENT_SESSION_REDUCER_ID]
15 |
16 | export const currentSafeAddress = createSelector(currentSession, ({ currentSafeAddress }) => currentSafeAddress)
17 |
--------------------------------------------------------------------------------
/src/routes/safe/components/Transactions/TxList/TxLocationProvider.tsx:
--------------------------------------------------------------------------------
1 | import { createContext, ReactElement, ReactNode, useState } from 'react'
2 | import { TxLocation } from 'src/logic/safe/store/models/types/gateway.d'
3 |
4 | export type TxLocationProps = {
5 | txLocation: TxLocation
6 | setTxLocation?: (txLocation: TxLocation) => void
7 | }
8 |
9 | export const TxLocationContext = createContext({
10 | txLocation: 'history',
11 | setTxLocation: () => {},
12 | })
13 |
14 | export const TxLocationProvider = ({ children }: { children: ReactNode }): ReactElement => {
15 | const [txLocation, setTxLocation] = useState('history')
16 |
17 | return {children}
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/AppLayout/Header/components/AnimatedLogo.tsx:
--------------------------------------------------------------------------------
1 | import { useEffect, useState, ReactElement } from 'react'
2 | import SafeLogo from '../assets/transition-logo.gif'
3 |
4 | const useInterval = (delay: number): number => {
5 | const [count, setCount] = useState(0)
6 |
7 | useEffect(() => {
8 | const id = setInterval(() => {
9 | setCount((prev) => prev + 1)
10 | }, delay)
11 |
12 | return () => clearInterval(id)
13 | }, [delay])
14 |
15 | return count
16 | }
17 |
18 | const AnimatedLogo = (): ReactElement => {
19 | const RESTART_DELAY = 2 * 60e3 // 2 minutes
20 | const restartKey = useInterval(RESTART_DELAY)
21 |
22 | return
23 | }
24 |
25 | export default AnimatedLogo
26 |
--------------------------------------------------------------------------------
/src/utils/__tests__/checksumAddress.test.ts:
--------------------------------------------------------------------------------
1 | import { checksumAddress, isChecksumAddress } from '../checksumAddress'
2 |
3 | describe('checksumAddress', () => {
4 | it('Returns a checksummed address', () => {
5 | const address = '0xbaddad0000000000000000000000000000000001'
6 | const checksummedAddress = checksumAddress(address)
7 |
8 | expect(checksummedAddress).toBe('0xbAddaD0000000000000000000000000000000001')
9 | expect(isChecksumAddress(checksummedAddress)).toBeTruthy()
10 | })
11 | it('Throws if an invalid address was provided', () => {
12 | const address = '0xbaddad'
13 |
14 | try {
15 | checksumAddress(address)
16 | } catch (e) {
17 | expect(e.message).toBe('Given address "0xbaddad" is not a valid Ethereum address.')
18 | }
19 | })
20 | })
21 |
--------------------------------------------------------------------------------
/src/logic/config/store/reducer/index.ts:
--------------------------------------------------------------------------------
1 | import { handleActions } from 'redux-actions'
2 | import { LOCAL_CONFIG_KEY, _getChainId } from 'src/config'
3 | import { CONFIG_ACTIONS } from '../actions'
4 | import { ConfigState, ConfigPayload } from './reducer.d'
5 |
6 | export const CONFIG_REDUCER_ID = LOCAL_CONFIG_KEY
7 |
8 | export const initialConfigState: ConfigState = {
9 | chainId: _getChainId(),
10 | }
11 |
12 | // Stored locally as to preserve chainId for non-EIP-3770 routes
13 | const configReducer = handleActions(
14 | {
15 | [CONFIG_ACTIONS.SET_CHAIN_ID]: (state, action) => {
16 | const networkId = action.payload
17 | return { ...state, chainId: networkId }
18 | },
19 | },
20 | initialConfigState,
21 | )
22 |
23 | export default configReducer
24 |
--------------------------------------------------------------------------------
/src/routes/safe/components/Apps/components/SecurityFeedbackModal/constants.ts:
--------------------------------------------------------------------------------
1 | export const SECURITY_PRACTICES = [
2 | {
3 | id: '1',
4 | title: 'Always load Safe Apps from trusted sources.',
5 | imageSrc: './safe-apps-security-practices/1.png',
6 | },
7 | {
8 | id: '2',
9 | title: 'Check the Safe App link you are trying to use.',
10 | subtitle: 'Do you know the domain and trust it?',
11 | },
12 | {
13 | id: '3',
14 | title: 'Always check transaction information while creating it, before proposing it to the Safe.',
15 | imageSrc: './safe-apps-security-practices/2.png',
16 | },
17 | {
18 | id: '4',
19 | title: 'Do a second check on transaction data before signing in the queue.',
20 | imageSrc: './safe-apps-security-practices/3.png',
21 | },
22 | ]
23 |
--------------------------------------------------------------------------------
/src/components/AppLayout/Header/components/WalletIcon/icons/icon-coinbase.svg:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/src/routes/safe/components/Balances/SendModal/screens/ModalHeader/style.ts:
--------------------------------------------------------------------------------
1 | import { lg, md, secondaryText } from 'src/theme/variables'
2 | import { createStyles } from '@material-ui/core'
3 |
4 | export const styles = createStyles({
5 | heading: {
6 | padding: `${md} ${lg} 12px`,
7 | justifyContent: 'flex-start',
8 | boxSizing: 'border-box',
9 | maxHeight: '74px',
10 | flexWrap: 'nowrap',
11 | },
12 | annotation: {
13 | color: secondaryText,
14 | marginRight: 'auto',
15 | marginLeft: '8px',
16 | whiteSpace: 'nowrap',
17 | flex: 1,
18 | },
19 | headingText: {
20 | whiteSpace: 'nowrap',
21 | },
22 | closeIcon: {
23 | height: '24px',
24 | width: '24px',
25 | },
26 | icon: {
27 | width: '20px',
28 | marginRight: '10px',
29 | },
30 | })
31 |
--------------------------------------------------------------------------------
/src/routes/safe/components/Balances/SendModal/screens/ContractInteraction/ReviewCustomTx/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 | value: {
9 | marginLeft: sm,
10 | },
11 | outerData: {
12 | borderRadius: '5px',
13 | minHeight: '21px',
14 | },
15 | data: {
16 | wordBreak: 'break-all',
17 | overflow: 'auto',
18 | fontSize: '16px',
19 | fontFamily: 'Averta',
20 | maxHeight: '100px',
21 | letterSpacing: 'normal',
22 | fontStretch: 'normal',
23 | lineHeight: '1.43',
24 | },
25 | buttonRow: {
26 | height: '84px',
27 | justifyContent: 'center',
28 | gap: '16px',
29 | },
30 | })
31 |
--------------------------------------------------------------------------------
/src/routes/safe/components/Settings/RemoveSafeModal/style.ts:
--------------------------------------------------------------------------------
1 | import { createStyles, makeStyles } from '@material-ui/core/styles'
2 |
3 | import { lg, md, sm } from 'src/theme/variables'
4 |
5 | export const useStyles = makeStyles(
6 | createStyles({
7 | heading: {
8 | boxSizing: 'border-box',
9 | justifyContent: 'space-between',
10 | minHeight: '74px',
11 | padding: `${sm} ${lg}`,
12 | },
13 | container: {
14 | minHeight: '369px',
15 | padding: `${sm}`,
16 | },
17 | manage: {
18 | fontSize: lg,
19 | },
20 | close: {
21 | height: '35px',
22 | width: '35px',
23 | },
24 | owner: {
25 | padding: md,
26 | alignItems: 'center',
27 | },
28 | description: {
29 | padding: md,
30 | },
31 | }),
32 | )
33 |
--------------------------------------------------------------------------------
/src/components/WalletSwitch/index.tsx:
--------------------------------------------------------------------------------
1 | import { ReactElement } from 'react'
2 | import { Button } from '@material-ui/core'
3 | import { Text } from '@gnosis.pm/safe-react-components'
4 | import { switchWalletChain } from 'src/logic/wallets/utils/network'
5 | import ChainIndicator from 'src/components/ChainIndicator'
6 | import { useSelector } from 'react-redux'
7 | import { currentChainId } from 'src/logic/config/store/selectors'
8 |
9 | const WalletSwitch = (): ReactElement => {
10 | const chainId = useSelector(currentChainId)
11 | return (
12 |
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 |
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 |
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 |
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 |
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 |
26 | )
27 | }
28 |
29 | export default GnoTable
30 |
--------------------------------------------------------------------------------
/src/components/CookiesBanner/assets/alert-red.svg:
--------------------------------------------------------------------------------
1 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
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 |
11 | )
12 |
--------------------------------------------------------------------------------
/src/routes/safe/components/Transactions/TxList/assets/circle-cross-red.svg:
--------------------------------------------------------------------------------
1 |
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 |
--------------------------------------------------------------------------------