diff --git a/components/brave_wallet/common/BUILD.gn b/components/brave_wallet/common/BUILD.gn index b4f474931bf1..155a8320b069 100644 --- a/components/brave_wallet/common/BUILD.gn +++ b/components/brave_wallet/common/BUILD.gn @@ -32,6 +32,7 @@ static_library("common") { ":mojom__generator", "//base", "//brave/third_party/ethash", + "//url", ] } diff --git a/components/brave_wallet/common/eth_request_helper.cc b/components/brave_wallet/common/eth_request_helper.cc index 0b17807116a4..ab0b5891de3c 100644 --- a/components/brave_wallet/common/eth_request_helper.cc +++ b/components/brave_wallet/common/eth_request_helper.cc @@ -16,6 +16,7 @@ #include "brave/components/brave_wallet/common/eth_address.h" #include "brave/components/brave_wallet/common/hex_utils.h" #include "brave/components/brave_wallet/common/web3_provider_constants.h" +#include "url/gurl.h" namespace { @@ -472,12 +473,19 @@ bool ParseWalletWatchAssetParams(const std::string& json, return false; } - // TODO(jocelyn): Parse image URL to be the logo URL once we supports - // downloading an icon from a remote URL. + std::string logo; + const std::string* image = options_dict->FindStringKey("image"); + if (image) { + GURL url = GURL(*image); + if (url.is_valid() && (url.SchemeIsHTTPOrHTTPS() || + base::StartsWith(*image, "data:image/"))) { + logo = url.spec(); + } + } - *token = mojom::ERCToken::New(eth_addr.ToChecksumAddress(), - *symbol /* name */, "" /* logo */, true, false, - *symbol, decimals, true, ""); + *token = + mojom::ERCToken::New(eth_addr.ToChecksumAddress(), *symbol /* name */, + logo, true, false, *symbol, decimals, true, ""); return true; } diff --git a/components/brave_wallet/common/eth_request_helper_unittest.cc b/components/brave_wallet/common/eth_request_helper_unittest.cc index 44a92c43cac7..48cea5ca8259 100644 --- a/components/brave_wallet/common/eth_request_helper_unittest.cc +++ b/components/brave_wallet/common/eth_request_helper_unittest.cc @@ -557,7 +557,6 @@ TEST(EthRequestHelperUnitTest, ParseEthSignTypedDataParams) { } TEST(EthRequestHelperUnitTest, ParseWalletWatchAssetParams) { - // Image will be ignored currently. std::string json = R"({ "id": "1", "jsonrpc": "2.0", @@ -567,7 +566,7 @@ TEST(EthRequestHelperUnitTest, ParseWalletWatchAssetParams) { "address": "0x0D8775F648430679A709E98d2b0Cb6250d2887EF", "symbol": "BAT", "decimals": 18, - "image": "https://test.png" + "image": "https://test.com/test.png" }, "type": "ERC20" } @@ -575,8 +574,7 @@ TEST(EthRequestHelperUnitTest, ParseWalletWatchAssetParams) { mojom::ERCTokenPtr expected_token = mojom::ERCToken::New( "0x0D8775F648430679A709E98d2b0Cb6250d2887EF", "BAT", - "" /* logo is empty because image parameter is currently ignored. */, - true, false, "BAT", 18, true, ""); + "https://test.com/test.png", true, false, "BAT", 18, true, ""); mojom::ERCTokenPtr token; std::string error_message; @@ -584,6 +582,8 @@ TEST(EthRequestHelperUnitTest, ParseWalletWatchAssetParams) { EXPECT_EQ(token, expected_token); EXPECT_TRUE(error_message.empty()); + expected_token->logo = ""; + // Test optional image and non-checksum address. json = R"({ "id": "1", @@ -760,8 +760,7 @@ TEST(EthRequestHelperUnitTest, ParseWalletWatchAssetParams) { "options": { "address": "0x0D8775F648430679A709E98d2b0Cb6250d2887EF", "symbol": "BAT", - "decimals": 18, - "image": "https://test.png" + "decimals": 18 }, "type": "ERC20" }] @@ -770,6 +769,69 @@ TEST(EthRequestHelperUnitTest, ParseWalletWatchAssetParams) { EXPECT_TRUE(ParseWalletWatchAssetParams(json, &token, &error_message)); EXPECT_EQ(token, expected_token); EXPECT_TRUE(error_message.empty()); + + // Test image parameter + json = R"({ + "id": "1", + "jsonrpc": "2.0", + "method": "wallet_watchAsset", + "params": { + "options": { + "address": "0x0d8775f648430679a709e98d2b0cb6250d2887ef", + "symbol": "BAT", + "decimals": 18, + "image": "http://test.com/test.png" + }, + "type": "ERC20" + } + })"; + expected_token->logo = "http://test.com/test.png"; + EXPECT_TRUE(ParseWalletWatchAssetParams(json, &token, &error_message)); + EXPECT_EQ(token, expected_token); + EXPECT_TRUE(error_message.empty()); + + json = R"({ + "id": "1", + "jsonrpc": "2.0", + "method": "wallet_watchAsset", + "params": { + "options": { + "address": "0x0d8775f648430679a709e98d2b0cb6250d2887ef", + "symbol": "BAT", + "decimals": 18, + "image": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR42mP4z8AAAAMBAQD3A0FDAAAAAElFTkSuQmCC" + }, + "type": "ERC20" + } + })"; + expected_token->logo = + "data:image/" + "png;base64," + "iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR42mP4z8AAAAMBAQD3" + "A0FDAAAAAElFTkSuQmCC"; + EXPECT_TRUE(ParseWalletWatchAssetParams(json, &token, &error_message)); + EXPECT_EQ(token, expected_token); + EXPECT_TRUE(error_message.empty()); + + // Invalid image parameter will have empty logo string. + json = R"({ + "id": "1", + "jsonrpc": "2.0", + "method": "wallet_watchAsset", + "params": { + "options": { + "address": "0x0d8775f648430679a709e98d2b0cb6250d2887ef", + "symbol": "BAT", + "decimals": 18, + "image": "test.png" + }, + "type": "ERC20" + } + })"; + expected_token->logo = ""; + EXPECT_TRUE(ParseWalletWatchAssetParams(json, &token, &error_message)); + EXPECT_EQ(token, expected_token); + EXPECT_TRUE(error_message.empty()); } } // namespace brave_wallet diff --git a/components/brave_wallet_ui/components/shared/create-placeholder-icon/index.tsx b/components/brave_wallet_ui/components/shared/create-placeholder-icon/index.tsx index 96b8ad6a9bdb..12241360ef7b 100644 --- a/components/brave_wallet_ui/components/shared/create-placeholder-icon/index.tsx +++ b/components/brave_wallet_ui/components/shared/create-placeholder-icon/index.tsx @@ -1,7 +1,7 @@ import * as React from 'react' import { ERCToken } from '../../../constants/types' import { IconWrapper, PlaceholderText } from './style' -import { stripERC20TokenImageURL } from '../../../utils/string-utils' +import { stripERC20TokenImageURL, isRemoteImageURL } from '../../../utils/string-utils' import { background } from 'ethereum-blockies' import { ETH } from '../../../options/asset-options' @@ -29,7 +29,7 @@ function withPlaceholderIcon (WrappedComponent: React.ComponentType, config return null } const tokenImageURL = stripERC20TokenImageURL(selectedAsset?.logo) - const checkIconURL = selectedAsset?.symbol !== 'ETH' && (tokenImageURL === '' || tokenImageURL?.startsWith('http')) + const checkIconURL = selectedAsset?.symbol !== 'ETH' && (tokenImageURL === '' || isRemoteImageURL(tokenImageURL)) const bg = React.useMemo(() => { if (checkIconURL) { diff --git a/components/brave_wallet_ui/panel/async/wallet_panel_async_handler.ts b/components/brave_wallet_ui/panel/async/wallet_panel_async_handler.ts index b63db86c2174..78e2ebcf05b3 100644 --- a/components/brave_wallet_ui/panel/async/wallet_panel_async_handler.ts +++ b/components/brave_wallet_ui/panel/async/wallet_panel_async_handler.ts @@ -51,6 +51,7 @@ import { getLocale } from '../../../common/locale' import getWalletPanelApiProxy from '../wallet_panel_api_proxy' import { HardwareVendor } from '../../common/api/hardware_keyrings' +import { isRemoteImageURL } from '../../utils/string-utils' const handler = new AsyncActionHandler() @@ -107,11 +108,8 @@ async function getPendingAddSuggestTokenRequest () { const requests = (await braveWalletService.getPendingAddSuggestTokenRequests()).requests if (requests && requests.length) { - // Currently it will only be non-empty if the logo info is coming from us, - // either from user asset list or the ERC token registry, re-map the logo - // url to show the icon correctly in the add suggest token UI. const logo = requests[0].token.logo - if (logo !== '') { + if (logo !== '' && !isRemoteImageURL(logo)) { requests[0].token.logo = `chrome://erc-token-images/${logo}` } return requests[0] diff --git a/components/brave_wallet_ui/utils/string-utils.test.ts b/components/brave_wallet_ui/utils/string-utils.test.ts new file mode 100644 index 000000000000..5f965ed2a18a --- /dev/null +++ b/components/brave_wallet_ui/utils/string-utils.test.ts @@ -0,0 +1,23 @@ +import { isRemoteImageURL } from './string-utils' + +describe('Checking URL is remote image or not', () => { + test('HTTP URL should return true', () => { + expect(isRemoteImageURL('http://test.com/test.png')).toEqual(true) + }) + + test('HTTPS URL should return true', () => { + expect(isRemoteImageURL('https://test.com/test.png')).toEqual(true) + }) + + test('Data URL image should return true', () => { + expect(isRemoteImageURL('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAIAAACQd1PeAAAADElEQVR42mP4z8AAAAMBAQD3A0FDAAAAAElFTkSuQmCC')).toEqual(true) + }) + + test('local path should return false', () => { + expect(isRemoteImageURL('bat.png')).toEqual(false) + }) + + test('undefined input should return undefined', () => { + expect(isRemoteImageURL(undefined)).toEqual(undefined) + }) +}) diff --git a/components/brave_wallet_ui/utils/string-utils.ts b/components/brave_wallet_ui/utils/string-utils.ts index 3b3aefe09c9c..60b30ed9a0b9 100644 --- a/components/brave_wallet_ui/utils/string-utils.ts +++ b/components/brave_wallet_ui/utils/string-utils.ts @@ -4,3 +4,6 @@ export const stripERC20TokenImageURL = (url?: string) => export const toProperCase = (value: string) => value.replace(/\w\S*/g, (txt) => txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase()) + +export const isRemoteImageURL = (url?: string) => + url?.startsWith('http://') || url?.startsWith('https://') || url?.startsWith('data:image/')