Merge branch 'development' of github.com:gnosis/safe-react into address-book-v2
This commit is contained in:
commit
548d6d26ed
|
@ -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=
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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",
|
||||
|
|
|
@ -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 |
|
@ -65,7 +65,7 @@ const WALLET_ICONS: WalletObjectsProps<IconValue> = {
|
|||
src: operaIcon,
|
||||
height: 25,
|
||||
},
|
||||
[WALLET_PROVIDER.WALLETLINK]: {
|
||||
[WALLET_PROVIDER.COINBASE_WALLET]: {
|
||||
src: coinbaseIcon,
|
||||
height: 25,
|
||||
},
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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()
|
||||
})
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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)
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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 () => {
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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(() => {
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -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 = {
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 />
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
},
|
||||
|
|
|
@ -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} />
|
||||
)
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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 =
|
||||
|
|
24
yarn.lock
24
yarn.lock
|
@ -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==
|
||||
|
|
Loading…
Reference in New Issue