Merge branch 'development' of github.com:gnosis/safe-react into address-book-v2

This commit is contained in:
katspaugh 2021-06-03 11:58:10 +02:00
commit 548d6d26ed
35 changed files with 209 additions and 125 deletions

View File

@ -16,6 +16,7 @@ REACT_APP_FORTMATIC_KEY=
REACT_APP_OPENSEA_API_KEY=
REACT_APP_COLLECTIBLES_SOURCE=
REACT_APP_ETHERSCAN_API_KEY=
REACT_APP_ETHGASSTATION_API_KEY=
# Versions
REACT_APP_LATEST_SAFE_VERSION=

View File

@ -21,6 +21,7 @@ env:
REACT_APP_SENTRY_DSN: ${{ secrets.SENTRY_DSN_RINKEBY }}
REACT_APP_GOOGLE_ANALYTICS: ${{ secrets.REACT_APP_GOOGLE_ANALYTICS_ID_RINKEBY }}
REACT_APP_GNOSIS_APPS_URL: ${{ secrets.REACT_APP_GNOSIS_APPS_URL_PROD }}
REACT_APP_ETHGASSTATION_API_KEY: ${{ secrets.REACT_APP_ETHGASSTATION_API_KEY_RINKEBY }}
jobs:
debug:

View File

@ -1,6 +1,6 @@
{
"name": "safe-react",
"version": "3.6.5",
"version": "3.6.7",
"description": "Allowing crypto users manage funds in a safer way",
"website": "https://github.com/gnosis/safe-react#readme",
"bugs": {
@ -158,7 +158,7 @@
]
},
"dependencies": {
"@gnosis.pm/safe-apps-sdk": "1.0.3",
"@gnosis.pm/safe-apps-sdk": "3.0.0-alpha.5",
"@gnosis.pm/safe-apps-sdk-v1": "npm:@gnosis.pm/safe-apps-sdk@0.4.2",
"@gnosis.pm/safe-contracts": "1.1.1-dev.2",
"@gnosis.pm/safe-react-components": "https://github.com/gnosis/safe-react-components.git#0e4fcd6",

View File

@ -1,31 +1,4 @@
<svg width="40" height="40" viewBox="0 0 383 383" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0)">
<g filter="url(#filter0_dd)">
<path d="M0.998047 0.572266L382.78 0.572266V382.354H0.998047L0.998047 0.572266Z" fill="url(#paint0_linear)"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M59.1074 191.572C59.1074 264.966 118.605 324.463 191.998 324.463C265.392 324.463 324.889 264.966 324.889 191.572C324.889 118.179 265.392 58.6816 191.998 58.6816C118.605 58.6816 59.1074 118.179 59.1074 191.572ZM158.037 148.752C153.144 148.752 149.178 152.718 149.178 157.611V225.533C149.178 230.426 153.144 234.393 158.037 234.393H225.959C230.852 234.393 234.818 230.426 234.818 225.533V157.611C234.818 152.718 230.852 148.752 225.959 148.752H158.037Z" fill="white"/>
</g>
</g>
<defs>
<filter id="filter0_dd" x="-23.002" y="-7.42773" width="429.782" height="429.782" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset dy="16"/>
<feGaussianBlur stdDeviation="12"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.06 0"/>
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow"/>
<feColorMatrix in="SourceAlpha" type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"/>
<feOffset/>
<feGaussianBlur stdDeviation="4"/>
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.04 0"/>
<feBlend mode="normal" in2="effect1_dropShadow" result="effect2_dropShadow"/>
<feBlend mode="normal" in="SourceGraphic" in2="effect2_dropShadow" result="shape"/>
</filter>
<linearGradient id="paint0_linear" x1="191.889" y1="0.572266" x2="191.889" y2="382.354" gradientUnits="userSpaceOnUse">
<stop stop-color="#2E66F8"/>
<stop offset="1" stop-color="#124ADB"/>
</linearGradient>
<clipPath id="clip0">
<rect width="381.782" height="381.782" fill="white" transform="translate(0.998047 0.572266)"/>
</clipPath>
</defs>
</svg>
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M20 40C31.0457 40 40 31.0457 40 20C40 8.9543 31.0457 0 20 0C8.9543 0 0 8.9543 0 20C0 31.0457 8.9543 40 20 40Z" fill="#1652F0"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M5.45508 20.0006C5.45508 28.0338 11.9673 34.546 20.0006 34.546C28.0338 34.546 34.546 28.0338 34.546 20.0006C34.546 11.9673 28.0338 5.45508 20.0006 5.45508C11.9673 5.45508 5.45508 11.9673 5.45508 20.0006ZM17.3137 15.3145C16.2091 15.3145 15.3137 16.2099 15.3137 17.3145V22.6882C15.3137 23.7928 16.2091 24.6882 17.3137 24.6882H22.6874C23.792 24.6882 24.6874 23.7928 24.6874 22.6882V17.3145C24.6874 16.2099 23.792 15.3145 22.6874 15.3145H17.3137Z" fill="white"/>
</svg>

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 749 B

View File

@ -65,7 +65,7 @@ const WALLET_ICONS: WalletObjectsProps<IconValue> = {
src: operaIcon,
height: 25,
},
[WALLET_PROVIDER.WALLETLINK]: {
[WALLET_PROVIDER.COINBASE_WALLET]: {
src: coinbaseIcon,
height: 25,
},

View File

@ -9,6 +9,7 @@ import { COOKIES_KEY } from 'src/logic/cookies/model/cookie'
import { openCookieBanner } from 'src/logic/cookies/store/actions/openCookieBanner'
import { cookieBannerOpen } from 'src/logic/cookies/store/selectors'
import { loadFromCookie, saveCookie } from 'src/logic/cookies/utils'
import { useSafeAppUrl } from 'src/logic/hooks/useSafeAppUrl'
import { mainFontFamily, md, primary, screenSm } from 'src/theme/variables'
import { loadGoogleAnalytics, removeCookies } from 'src/utils/googleAnalytics'
import { closeIntercom, isIntercomLoaded, loadIntercom } from 'src/utils/intercom'
@ -97,7 +98,7 @@ interface CookiesBannerFormProps {
const CookiesBanner = (): ReactElement => {
const classes = useStyles()
const dispatch = useRef(useDispatch())
const { url: appUrl } = useSafeAppUrl()
const [showAnalytics, setShowAnalytics] = useState(false)
const [showIntercom, setShowIntercom] = useState(false)
const [localNecessary, setLocalNecessary] = useState(true)
@ -106,6 +107,12 @@ const CookiesBanner = (): ReactElement => {
const showBanner = useSelector(cookieBannerOpen)
useEffect(() => {
if (appUrl) {
setTimeout(closeIntercom, 50)
}
}, [appUrl])
useEffect(() => {
async function fetchCookiesFromStorage() {
const cookiesState = await loadFromCookie(COOKIES_KEY)
@ -171,7 +178,7 @@ const CookiesBanner = (): ReactElement => {
dispatch.current(openCookieBanner({ cookieBannerOpen: false }))
}
if (showIntercom) {
if (showIntercom && !appUrl) {
loadIntercom()
}

View File

@ -1,5 +1,6 @@
import EtherLogo from 'src/config/assets/token_eth.svg'
import { EnvironmentSettings, ETHEREUM_NETWORK, NetworkConfig } from 'src/config/networks/network.d'
import { ETHGASSTATION_API_KEY } from 'src/utils/constants'
const baseConfig: EnvironmentSettings = {
clientGatewayUrl: 'https://safe-client.mainnet.staging.gnosisdev.com/v1',

View File

@ -1,12 +1,13 @@
import EtherLogo from 'src/config/assets/token_eth.svg'
import { EnvironmentSettings, ETHEREUM_NETWORK, NetworkConfig, WALLETS } from 'src/config/networks/network.d'
import { ETHGASSTATION_API_KEY } from 'src/utils/constants'
const baseConfig: EnvironmentSettings = {
clientGatewayUrl: 'https://safe-client.rinkeby.staging.gnosisdev.com/v1',
txServiceUrl: 'https://safe-transaction.rinkeby.staging.gnosisdev.com/api/v1',
safeAppsUrl: 'https://safe-apps.dev.gnosisdev.com',
gasPriceOracle: {
url: 'https://ethgasstation.info/json/ethgasAPI.json',
url: `https://ethgasstation.info/json/ethgasAPI.json?api-key=${ETHGASSTATION_API_KEY}`,
gasParameter: 'average',
},
rpcServiceUrl: 'https://rinkeby.infura.io:443/v3',

View File

@ -26,6 +26,33 @@ describe('CodedException', () => {
expect(err.code).toBe(100)
})
it('creates an error with an extra message and a context', () => {
const context = {
tags: {
error_category: 'Safe Apps',
},
contexts: {
safeApp: {
name: 'Zorbed.Finance',
url: 'https://zorbed.finance',
},
message: {
method: 'getSafeBalance',
params: {
address: '0x000000',
},
},
},
}
const err = new CodedException(Errors._901, 'getSafeBalance: Server responded with 429 Too Many Requests', context)
expect(err.message).toBe(
'901: Error processing Safe Apps SDK request (getSafeBalance: Server responded with 429 Too Many Requests)',
)
expect(err.code).toBe(901)
expect(err.context).toEqual(context)
})
describe('Logging', () => {
beforeAll(() => {
jest.mock('console')
@ -70,7 +97,7 @@ describe('CodedException', () => {
it("doesn't track when isTracked is false", () => {
;(constants as any).IS_PRODUCTION = true
logError(Errors._100, '', false)
logError(Errors._100, '', undefined, false)
expect(Sentry.captureException).not.toHaveBeenCalled()
})

View File

@ -1,23 +1,28 @@
import * as Sentry from '@sentry/react'
import { CaptureContext } from '@sentry/types'
import ErrorCodes from './registry'
import { IS_PRODUCTION } from 'src/utils/constants'
export class CodedException extends Error {
public readonly message: string
public readonly code: number
// the context allows to enrich events, for the list of allowed context keys/data, please check the type or go to
// https://docs.sentry.io/platforms/javascript/enriching-events/context/
// The context is not searchable, that means its goal is just to provide additional data for the error
public readonly context?: CaptureContext
constructor(content: ErrorCodes, extraMessage?: string) {
constructor(content: ErrorCodes, extraMessage?: string, context?: CaptureContext) {
super()
const codePrefix = content.split(':')[0]
const code = Number(codePrefix)
if (isNaN(code)) {
throw new CodedException(ErrorCodes.___0, codePrefix)
throw new CodedException(ErrorCodes.___0, codePrefix, context)
}
const extraInfo = extraMessage ? ` (${extraMessage})` : ''
this.message = `${content}${extraInfo}`
this.code = code
this.context = context
}
/**
@ -28,13 +33,18 @@ export class CodedException extends Error {
console.error(IS_PRODUCTION ? this.message : this)
if (IS_PRODUCTION && isTracked) {
Sentry.captureException(this)
Sentry.captureException(this, this.context)
}
}
}
export function logError(content: ErrorCodes, extraMessage?: string, isTracked?: boolean): CodedException {
const error = new CodedException(content, extraMessage)
export function logError(
content: ErrorCodes,
extraMessage?: string,
context?: CaptureContext,
isTracked?: boolean,
): CodedException {
const error = new CodedException(content, extraMessage, context)
error.log(isTracked)
return error
}

View File

@ -10,6 +10,8 @@ enum ErrorCodes {
_200 = '200: Failed migrating to the address book v2',
_600 = '600: Error fetching token list',
_601 = '601: Error fetching balances',
_900 = '900: Error loading Safe App',
_901 = '901: Error processing Safe Apps SDK request',
}
export default ErrorCodes

View File

@ -0,0 +1,22 @@
import { useLocation } from 'react-router-dom'
import { useEffect, useState } from 'react'
type AppUrlReturnType = {
url: string | null
}
export const useSafeAppUrl = (): AppUrlReturnType => {
const [url, setUrl] = useState<string | null>(null)
const { search } = useLocation()
useEffect(() => {
if (search !== url) {
const query = new URLSearchParams(search)
setUrl(query.get('appUrl'))
}
}, [search, url])
return {
url,
}
}

View File

@ -2,50 +2,53 @@ import axios from 'axios'
import { getSafeClientGatewayBaseUrl } from 'src/config'
import { fetchTokenCurrenciesBalances } from 'src/logic/safe/api/fetchTokenCurrenciesBalances'
import { aNewStore } from 'src/store'
jest.mock('axios')
describe('fetchTokenCurrenciesBalances', () => {
let store
const safeAddress = '0xdfA693da0D16F5E7E78FdCBeDe8FC6eBEa44f1Cf'
const excludeSpamTokens = true
beforeEach(() => {
store = aNewStore()
})
afterAll(() => {
jest.unmock('axios')
})
it('Given a safe address, calls the API and returns token balances', async () => {
// given
const expectedResult = [
{
tokenAddress: '',
balance: '849890000000000000',
fiatBalance: '337.2449',
fiatConversion: '396.81',
fiatCode: 'USD',
},
{
tokenAddress: '0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa',
token: {
address: '0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa',
balance: '24698677800000000000',
name: 'Dai',
symbol: 'DAI',
decimals: 18,
logoUri: 'https://gnosis-safe-token-logos.s3.amazonaws.com/0x5592EC0cfb4dbc12D3aB100b257153436a1f0FEa.png',
const expectedResult = {
fiatTotal: '104.32679999999999',
items: [
{
tokenInfo: {
type: 'ERC20',
address: '0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e',
decimals: 18,
symbol: 'YFI',
name: 'yearn.finance',
logoUri: 'https://gnosis-safe-token-logos.s3.amazonaws.com/0x0bc529c00C6401aEF6D220BE8C6Ea1667F6Ad93e.png',
},
balance: '465000000000000',
fiatBalance: '24.0178',
fiatConversion: '51651.1013',
},
balance: '24698677800000000000',
fiatBalance: '29.3432',
fiatConversion: '1.188',
fiatCode: 'USD',
},
]
{
tokenInfo: {
type: 'ETHER',
address: '0x0000000000000000000000000000000000000000',
decimals: 18,
symbol: 'ETH',
name: 'Ether',
logoUri: null,
},
balance: '4035779634142020',
fiatBalance: '10.9702',
fiatConversion: '2718.2447',
},
],
}
const apiUrl = getSafeClientGatewayBaseUrl(safeAddress)
// @ts-ignore
// @ts-expect-error mocking get method
axios.get.mockImplementationOnce(() => Promise.resolve({ data: expectedResult }))
// when

View File

@ -15,10 +15,7 @@ type FetchSafeTxGasEstimationProps = {
operation: number
}
export const fetchSafeTxGasEstimation = async ({
safeAddress,
...body
}: FetchSafeTxGasEstimationProps): Promise<string> => {
export const fetchSafeTxGasEstimation = ({ safeAddress, ...body }: FetchSafeTxGasEstimationProps): Promise<string> => {
const url = `${getSafeServiceBaseUrl(checksumAddress(safeAddress))}/multisig-transactions/estimations/`
return axios.post(url, body).then(({ data }) => data.safeTxGas)

View File

@ -5,7 +5,7 @@ import { TokenProps } from 'src/logic/tokens/store/model/token'
import { checksumAddress } from 'src/utils/checksumAddress'
export type TokenBalance = {
tokenInfo: TokenProps
tokenInfo: Omit<TokenProps, 'balance'>
balance: string
fiatBalance: string
fiatConversion: string
@ -23,7 +23,7 @@ type FetchTokenCurrenciesBalancesProps = {
trustedTokens?: boolean
}
export const fetchTokenCurrenciesBalances = async ({
export const fetchTokenCurrenciesBalances = ({
safeAddress,
selectedCurrency,
excludeSpamTokens = true,

View File

@ -70,7 +70,7 @@ export const fetchSafeTokens = (safeAddress: string, currencySelected?: string)
selectedCurrency: currencySelected ?? selectedCurrency,
})
} catch (e) {
logError(Errors._601, e.message, false)
logError(Errors._601, e.message, undefined, false)
return
}

View File

@ -61,7 +61,7 @@ export const fetchTokens = () => async (
const resp = await fetchErc20AndErc721AssetsList()
tokenList = resp.data.results
} catch (e) {
logError(Errors._600, e.message, false)
logError(Errors._600, e.message, undefined, false)
return
}

View File

@ -9,6 +9,7 @@ import { getRpcServiceUrl } from 'src/config'
import { isValidCryptoDomainName } from 'src/logic/wallets/ethAddresses'
import { getAddressFromUnstoppableDomain } from './utils/unstoppableDomains'
// This providers have direct relation with name assigned in bnc-onboard configuration
export const WALLET_PROVIDER = {
SAFE: 'SAFE',
METAMASK: 'METAMASK',
@ -19,7 +20,8 @@ export const WALLET_PROVIDER = {
SQUARELINK: 'SQUARELINK',
WALLETCONNECT: 'WALLETCONNECT',
OPERA: 'OPERA',
WALLETLINK: 'WALLETLINK',
// This is the provider for WALLET_LINK configuration on bnc-onboard
COINBASE_WALLET: 'COINBASE WALLET',
AUTHEREUM: 'AUTHEREUM',
LEDGER: 'LEDGER',
TREZOR: 'TREZOR',

View File

@ -1,5 +1,5 @@
import { isAppManifestValid } from '../utils'
import { SafeApp } from '../types.d'
import { SafeApp } from '../types'
describe('SafeApp manifest', () => {
it('It should return true given a manifest with mandatory values supplied', async () => {

View File

@ -7,8 +7,10 @@ import {
ErrorResponse,
MessageFormatter,
METHODS,
RequestId,
} from '@gnosis.pm/safe-apps-sdk'
import { SafeApp } from './types.d'
import { logError, Errors } from 'src/logic/exceptions/CodedException'
import { SafeApp } from './types'
type MessageHandler = (
msg: SDKMessageEvent,
@ -42,10 +44,10 @@ class AppCommunicator {
return Boolean(this.handlers.get(msg.data.method))
}
send = (data, requestId, error = false): void => {
send = (data: unknown, requestId: RequestId, error = false): void => {
const sdkVersion = getSDKVersion()
const msg = error
? MessageFormatter.makeErrorResponse(requestId, data, sdkVersion)
? MessageFormatter.makeErrorResponse(requestId, data as string, sdkVersion)
: MessageFormatter.makeResponse(requestId, data, sdkVersion)
this.iframeRef.current?.contentWindow?.postMessage(msg, '*')
@ -66,8 +68,13 @@ class AppCommunicator {
this.send(response, msg.data.id)
}
} catch (err) {
console.log({ err })
this.send(err.message, msg.data.id, true)
logError(Errors._901, err.message, {
contexts: {
safeApp: this.app,
request: msg.data,
},
})
}
}
}

View File

@ -4,7 +4,7 @@ import React from 'react'
import { useField, useFormState } from 'react-final-form'
import styled from 'styled-components'
import { SafeApp } from 'src/routes/safe/components/Apps/types.d'
import { SafeApp } from 'src/routes/safe/components/Apps/types'
import { getAppInfoFromUrl, getIpfsLinkFromEns, uniqueApp } from 'src/routes/safe/components/Apps/utils'
import { composeValidators, required } from 'src/components/forms/validator'
import Field from 'src/components/forms/Field'

View File

@ -2,7 +2,7 @@ import React, { ReactElement, useMemo } from 'react'
import { useFormState } from 'react-final-form'
import { Modal } from 'src/components/Modal'
import { SafeApp } from 'src/routes/safe/components/Apps/types.d'
import { SafeApp } from 'src/routes/safe/components/Apps/types'
import { isAppManifestValid } from 'src/routes/safe/components/Apps/utils'
interface Props {

View File

@ -2,7 +2,7 @@ import { Icon, Link, Loader, Text, TextField } from '@gnosis.pm/safe-react-compo
import React, { useState, ReactElement } from 'react'
import styled from 'styled-components'
import { SafeApp } from 'src/routes/safe/components/Apps/types.d'
import { SafeApp } from 'src/routes/safe/components/Apps/types'
import GnoForm from 'src/components/forms/GnoForm'
import Img from 'src/components/layout/Img'
import { Modal } from 'src/components/Modal'

View File

@ -1,7 +1,7 @@
import React, { ReactElement, useState, useRef, useCallback, useEffect } from 'react'
import styled from 'styled-components'
import { FixedIcon, Loader, Title, Card } from '@gnosis.pm/safe-react-components'
import { MethodToResponse, RPCPayload } from '@gnosis.pm/safe-apps-sdk'
import { GetBalanceParams, MethodToResponse, RPCPayload } from '@gnosis.pm/safe-apps-sdk'
import { useHistory } from 'react-router-dom'
import { useSelector } from 'react-redux'
import { INTERFACE_MESSAGES, Transaction, RequestId, LowercaseNetworks } from '@gnosis.pm/safe-apps-sdk-v1'
@ -23,8 +23,9 @@ import { useIframeMessageHandler } from '../hooks/useIframeMessageHandler'
import { useLegalConsent } from '../hooks/useLegalConsent'
import LegalDisclaimer from './LegalDisclaimer'
import { getAppInfoFromUrl } from '../utils'
import { SafeApp } from '../types.d'
import { SafeApp } from '../types'
import { useAppCommunicator } from '../communicator'
import { fetchTokenCurrenciesBalances } from 'src/logic/safe/api/fetchTokenCurrenciesBalances'
const OwnerDisclaimer = styled.div`
display: flex;
@ -62,7 +63,7 @@ export type TransactionParams = {
type ConfirmTransactionModalState = {
isOpen: boolean
txs: Transaction[]
requestId?: RequestId
requestId: RequestId
params?: TransactionParams
}
@ -76,7 +77,7 @@ const NETWORK_ID = getNetworkId()
const INITIAL_CONFIRM_TX_MODAL_STATE: ConfirmTransactionModalState = {
isOpen: false,
txs: [],
requestId: undefined,
requestId: '',
params: undefined,
}
@ -167,6 +168,14 @@ const AppFrame = ({ appUrl }: Props): ReactElement => {
chainId: NETWORK_ID,
}))
communicator?.on('getSafeBalances', async (msg) => {
const { currency = 'usd' } = msg.data.params as GetBalanceParams
const balances = await fetchTokenCurrenciesBalances({ safeAddress, selectedCurrency: currency })
return balances
})
communicator?.on('rpcCall', async (msg) => {
const params = msg.data.params as RPCPayload
@ -215,7 +224,7 @@ const AppFrame = ({ appUrl }: Props): ReactElement => {
)
// Safe Apps SDK V2 Handler
communicator?.send({ safeTxHash }, confirmTransactionModal.requestId)
communicator?.send({ safeTxHash }, confirmTransactionModal.requestId as string)
}
const onTxReject = () => {
@ -226,7 +235,7 @@ const AppFrame = ({ appUrl }: Props): ReactElement => {
)
// Safe Apps SDK V2 Handler
communicator?.send('Transaction was rejected', confirmTransactionModal.requestId, true)
communicator?.send('Transaction was rejected', confirmTransactionModal.requestId as string, true)
}
useEffect(() => {

View File

@ -12,7 +12,7 @@ import { useRouteMatch, Link } from 'react-router-dom'
import { SAFELIST_ADDRESS } from 'src/routes/routes'
import { useAppList } from '../hooks/useAppList'
import { SAFE_APP_FETCH_STATUS, SafeApp } from '../types.d'
import { SAFE_APP_FETCH_STATUS, SafeApp } from '../types'
import AddAppForm from './AddAppForm'
import { AppData } from '../api/fetchSafeAppsList'

View File

@ -2,7 +2,7 @@ import React, { ReactElement, useState } from 'react'
import { Transaction } from '@gnosis.pm/safe-apps-sdk-v1'
import Modal from 'src/components/Modal'
import { SafeApp } from 'src/routes/safe/components/Apps/types.d'
import { SafeApp } from 'src/routes/safe/components/Apps/types'
import { TransactionParams } from 'src/routes/safe/components/Apps/components/AppFrame'
import { mustBeEthereumAddress } from 'src/components/forms/validator'
import { SafeAppLoadError } from './SafeAppLoadError'

View File

@ -2,7 +2,7 @@ import { useState, useEffect, useCallback } from 'react'
import { loadFromStorage, saveToStorage } from 'src/utils/storage'
import { APPS_STORAGE_KEY, getAppInfoFromUrl, getAppsList, getEmptySafeApp } from '../utils'
import { AppData } from '../api/fetchSafeAppsList'
import { SafeApp, StoredSafeApp, SAFE_APP_FETCH_STATUS } from '../types.d'
import { SafeApp, StoredSafeApp, SAFE_APP_FETCH_STATUS } from '../types'
import { getNetworkId } from 'src/config'
type UseAppListReturnType = {

View File

@ -17,7 +17,7 @@ import { getNetworkName, getTxServiceUrl } from 'src/config/'
import { useSafeName } from 'src/logic/addressBook/hooks/useSafeName'
import { safeEthBalanceSelector, safeParamAddressFromStateSelector } from 'src/logic/safe/store/selectors'
import { TransactionParams } from '../components/AppFrame'
import { SafeApp } from 'src/routes/safe/components/Apps/types.d'
import { SafeApp } from 'src/routes/safe/components/Apps/types'
type InterfaceMessageProps<T extends InterfaceMessageIds> = {
messageId: T

View File

@ -1,20 +1,14 @@
import React from 'react'
import { useLocation } from 'react-router-dom'
import { useSafeAppUrl } from 'src/logic/hooks/useSafeAppUrl'
import AppFrame from './components/AppFrame'
import AppsList from './components/AppsList'
const useQuery = () => {
return new URLSearchParams(useLocation().search)
}
const Apps = (): React.ReactElement => {
const query = useQuery()
const appUrl = query.get('appUrl')
const { url } = useSafeAppUrl()
if (appUrl) {
return <AppFrame appUrl={appUrl} />
if (url) {
return <AppFrame appUrl={url} />
} else {
return <AppsList />
}

View File

@ -1,16 +1,17 @@
import axios from 'axios'
import memoize from 'lodash.memoize'
import { SafeApp, SAFE_APP_FETCH_STATUS } from './types.d'
import { SafeApp, SAFE_APP_FETCH_STATUS } from './types'
import { getContentFromENS } from 'src/logic/wallets/getWeb3'
import appsIconSvg from 'src/assets/icons/apps.svg'
import { ETHEREUM_NETWORK } from 'src/config/networks/network.d'
import { logError, Errors } from 'src/logic/exceptions/CodedException'
import { AppData, fetchSafeAppsList } from './api/fetchSafeAppsList'
export const APPS_STORAGE_KEY = 'APPS_STORAGE_KEY'
const removeLastTrailingSlash = (url) => {
const removeLastTrailingSlash = (url: string): string => {
if (url.substr(-1) === '/') {
return url.substr(0, url.length - 1)
}
@ -273,7 +274,13 @@ export const getAppInfoFromUrl = memoize(
}
return res
} catch (error) {
console.error(`It was not possible to fetch app from ${res.url}: ${error.message}`)
logError(Errors._900, error.message, {
contexts: {
safeApp: {
url: appUrl,
},
},
})
return res
}
},

View File

@ -49,8 +49,23 @@ const GenericValue = ({ method, type, value }: RenderValueProps): React.ReactEle
}
const Value = ({ type, ...props }: RenderValueProps): React.ReactElement => {
const explorerUrl = getExplorerInfo(props.value as string)
if (isArrayParameter(type) && isAddress(type)) {
return (
<div>
[
<NestedWrapper>
{(props.value as string[]).map((address) => {
const explorerUrl = getExplorerInfo(address)
return <EthHashInfo key={address} textSize="xl" hash={address} showCopyBtn explorerUrl={explorerUrl} />
})}
</NestedWrapper>
]
</div>
)
}
if (isAddress(type)) {
const explorerUrl = getExplorerInfo(props.value as string)
return (
<EthHashInfo textSize="xl" hash={props.value as string} showCopyBtn explorerUrl={explorerUrl} shortenHash={4} />
)

View File

@ -86,7 +86,7 @@ export const QueueTxList = ({ transactions }: QueueTxListProps): ReactElement =>
const title =
txLocation === 'queued.next'
? 'NEXT TRANSACTION'
: `QUEUE - Transaction with nonce ${nonce} needs to be executed fisrt`
: `QUEUE - Transaction with nonce ${nonce} needs to be executed first`
const { lastItemId, setLastItemId } = useContext(TxsInfiniteScrollContext)
if (transactions.length) {

View File

@ -23,6 +23,7 @@ export const OPENSEA_API_KEY = process.env.REACT_APP_OPENSEA_API_KEY || ''
export const COLLECTIBLES_SOURCE = process.env.REACT_APP_COLLECTIBLES_SOURCE || 'Gnosis'
export const TIMEOUT = process.env.NODE_ENV === 'test' ? 1500 : 5000
export const ETHERSCAN_API_KEY = process.env.REACT_APP_ETHERSCAN_API_KEY
export const ETHGASSTATION_API_KEY = process.env.REACT_APP_ETHGASSTATION_API_KEY
export const EXCHANGE_RATE_URL = 'https://api.exchangeratesapi.io/latest'
export const EXCHANGE_RATE_URL_FALLBACK = 'https://api.coinbase.com/v2/exchange-rates'
export const SAFE_APPS_LIST_URL =

View File

@ -1617,13 +1617,12 @@
resolved "https://registry.yarnpkg.com/@gnosis.pm/safe-apps-sdk/-/safe-apps-sdk-0.4.2.tgz#ae87b2164931c006cb0efdede3d82ff210df1648"
integrity sha512-BwA2dyCebPMdi4JhhTkp6EjkhEM6vAIviKdhqHiHnSmL+sDfxtP1jdOuE8ME2/4+5TiLSS8k8qscYjLSlf1LLw==
"@gnosis.pm/safe-apps-sdk@1.0.3":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@gnosis.pm/safe-apps-sdk/-/safe-apps-sdk-1.0.3.tgz#006d709700419302af490f6beaac67b1db4b36fb"
integrity sha512-77c6lg4SfCbQtMjgB2vVPKQglUCOjSpwweNeoD4m0c5hnd5lFg0Dlcc45LwtcUL8j5XhDf+aBxT8LDD+XGDSNw==
"@gnosis.pm/safe-apps-sdk@3.0.0-alpha.5":
version "3.0.0-alpha.5"
resolved "https://registry.yarnpkg.com/@gnosis.pm/safe-apps-sdk/-/safe-apps-sdk-3.0.0-alpha.5.tgz#f939e3a5719f51686151b76cb40af2ca4d410d30"
integrity sha512-yVvnJCl3B9Xk68vh+03rej4REx7cTfiR55b9TjqPaBtZLtY+c57UHCslUmkK+0MRMF4SyKaV5S9BOqI/iX9a3Q==
dependencies:
semver "^7.3.2"
web3-core "^1.3.0"
semver "7.1.1"
"@gnosis.pm/safe-contracts@1.1.1-dev.2":
version "1.1.1-dev.2"
@ -7937,9 +7936,9 @@ dns-equal@^1.0.0:
integrity sha1-s55/HabrCnW6nBcySzR1PEfgZU0=
dns-packet@^1.3.1:
version "1.3.1"
resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.1.tgz#12aa426981075be500b910eedcd0b47dd7deda5a"
integrity sha512-0UxfQkMhYAUaZI+xrNZOz/as5KgDU0M/fQ9b6SpkyLbk3GEswDi6PADJVaYJradtRVsRIlF1zLyOodbcTCDzUg==
version "1.3.4"
resolved "https://registry.yarnpkg.com/dns-packet/-/dns-packet-1.3.4.tgz#e3455065824a2507ba886c55a89963bb107dec6f"
integrity sha512-BQ6F4vycLXBvdrJZ6S3gZewt6rcrks9KBgM9vrhW+knGRqc8uEdT7fuCwloc7nny5xNoMJ17HGH0R/6fpo8ECA==
dependencies:
ip "^1.1.0"
safe-buffer "^5.0.1"
@ -18021,6 +18020,11 @@ semver@7.0.0:
resolved "https://registry.yarnpkg.com/semver/-/semver-7.0.0.tgz#5f3ca35761e47e05b206c6daff2cf814f0316b8e"
integrity sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==
semver@7.1.1:
version "7.1.1"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.1.1.tgz#29104598a197d6cbe4733eeecbe968f7b43a9667"
integrity sha512-WfuG+fl6eh3eZ2qAf6goB7nhiCd7NPXhmyFxigB/TOkQyeLP8w8GsVehvtGNtnNmyboz4TgeK40B1Kbql/8c5A==
semver@7.3.2:
version "7.3.2"
resolved "https://registry.yarnpkg.com/semver/-/semver-7.3.2.tgz#604962b052b81ed0786aae84389ffba70ffd3938"
@ -20583,7 +20587,7 @@ web3-core@1.2.9:
web3-core-requestmanager "1.2.9"
web3-utils "1.2.9"
web3-core@1.3.4, web3-core@^1.2.11, web3-core@^1.3.0:
web3-core@1.3.4, web3-core@^1.2.11:
version "1.3.4"
resolved "https://registry.yarnpkg.com/web3-core/-/web3-core-1.3.4.tgz#2cc7ba7f35cc167f7a0a46fd5855f86e51d34ce8"
integrity sha512-7OJu46RpCEfTerl+gPvHXANR2RkLqAfW7l2DAvQ7wN0pnCzl9nEfdgW6tMhr31k3TR2fWucwKzCyyxMGzMHeSA==