Add support for Solana NFT in NFT detail screen (#16747)

Adds support for Solana NFT in NFT detail screen.
Compared to the ETH NFT the following changes have been introduced:

* Shows SPL as new token standard
* Contract address label has become Mint address
* Mint address link points to NFT contract address using a different logic (consistent with iOS version)
* Mint address shown is truncated in the middle (consistent with iOS version)
This commit is contained in:
Simone Arpe
2023-01-24 15:40:05 +01:00
committed by GitHub
parent 782bc9ba25
commit dd008f15fe
9 changed files with 196 additions and 69 deletions
@@ -18,6 +18,7 @@ import org.chromium.brave_wallet.mojom.AssetRatioService;
import org.chromium.brave_wallet.mojom.BlockchainRegistry;
import org.chromium.brave_wallet.mojom.BlockchainToken;
import org.chromium.brave_wallet.mojom.BraveWalletService;
import org.chromium.brave_wallet.mojom.CoinType;
import org.chromium.brave_wallet.mojom.EthTxManagerProxy;
import org.chromium.brave_wallet.mojom.JsonRpcService;
import org.chromium.brave_wallet.mojom.KeyringService;
@@ -80,29 +81,46 @@ public class PortfolioModel implements BraveWalletServiceObserverImplDelegate {
public void prepareNftListMetaData(List<BlockchainToken> nftList, NetworkInfo networkInfo,
PortfolioHelper portfolioHelper) {
mPortfolioHelper = portfolioHelper;
List<BlockchainToken> ercNfts = JavaUtils.filter(nftList, nft -> nft.isErc721);
// Filter out and calculate the size of supported NFTs.
// The total sum will be used by `MultiResponseHandler` to detect
// when `setWhenAllCompletedAction()` can be processed.
int erc721NftsSize = JavaUtils.filter(nftList, (nft -> nft.isErc721)).size();
int solanaNftsSize = JavaUtils.filter(nftList, (nft -> nft.coin == CoinType.SOL)).size();
List<NftDataModel> nftDataModels = new ArrayList<>();
AsyncUtils.MultiResponseHandler nftMetaDataHandler =
new AsyncUtils.MultiResponseHandler(ercNfts.size());
new AsyncUtils.MultiResponseHandler(erc721NftsSize + solanaNftsSize);
ArrayList<AsyncUtils.GetNftMetaDataContext> nftMetaDatas = new ArrayList<>();
ArrayList<AsyncUtils.BaseGetNftMetadataContext> nftMetadataList = new ArrayList<>();
for (BlockchainToken userAsset : nftList) {
if (userAsset.isErc721) {
AsyncUtils.GetNftMetaDataContext nftMetaData = new AsyncUtils.GetNftMetaDataContext(
nftMetaDataHandler.singleResponseComplete);
nftMetaData.asset = userAsset;
AsyncUtils.GetNftErc721MetadataContext nftMetadata =
new AsyncUtils.GetNftErc721MetadataContext(
nftMetaDataHandler.singleResponseComplete);
nftMetadata.asset = userAsset;
mJsonRpcService.getErc721Metadata(userAsset.contractAddress, userAsset.tokenId,
userAsset.chainId, nftMetaData);
nftMetaDatas.add(nftMetaData);
} else if (userAsset.isNft) { // other nfts e.g. solana
nftDataModels.add(new NftDataModel(userAsset, networkInfo, null));
userAsset.chainId, nftMetadata);
nftMetadataList.add(nftMetadata);
} else if (userAsset.isNft) {
if (userAsset.coin == CoinType.SOL) {
// Solana NFTs.
AsyncUtils.GetNftSolanaMetadataContext nftMetadata =
new AsyncUtils.GetNftSolanaMetadataContext(
nftMetaDataHandler.singleResponseComplete);
nftMetadata.asset = userAsset;
mJsonRpcService.getSolTokenMetadata(userAsset.contractAddress, nftMetadata);
nftMetadataList.add(nftMetadata);
} else {
// Other NFTs.
nftDataModels.add(new NftDataModel(userAsset, networkInfo, null));
}
}
}
nftMetaDataHandler.setWhenAllCompletedAction(() -> {
for (AsyncUtils.GetNftMetaDataContext metaData : nftMetaDatas) {
nftDataModels.add(new NftDataModel(metaData.asset, networkInfo,
new Erc721MetaData(metaData.erc721Metadata, metaData.errorCode,
metaData.errorMessage)));
for (AsyncUtils.BaseGetNftMetadataContext metadata : nftMetadataList) {
nftDataModels.add(new NftDataModel(metadata.asset, networkInfo,
new NftMetadata(metadata.tokenMetadata, metadata.errorCode,
metadata.errorMessage)));
}
_mNftModels.postValue(nftDataModels);
});
@@ -143,24 +161,25 @@ public class PortfolioModel implements BraveWalletServiceObserverImplDelegate {
public static class NftDataModel {
public BlockchainToken token;
public NetworkInfo networkInfo;
public Erc721MetaData erc721MetaData;
public NftMetadata nftMetadata;
public NftDataModel(
BlockchainToken token, NetworkInfo networkInfo, Erc721MetaData erc721MetaData) {
BlockchainToken token, NetworkInfo networkInfo, NftMetadata nftMetadata) {
this.token = token;
this.networkInfo = networkInfo;
this.erc721MetaData = erc721MetaData;
this.nftMetadata = nftMetadata;
}
}
public static class Erc721MetaData implements Serializable {
public String mDescription;
public static class NftMetadata implements Serializable {
public String mImageUrl;
public String mName;
public int mErrCode;
public String mErrMsg;
// ERC721 only, but it may be present anyway in other standards.
public String mDescription;
public Erc721MetaData(String jsonString, int mErrCode, String mErrMsg) {
public NftMetadata(String jsonString, int mErrCode, String mErrMsg) {
this.mErrCode = mErrCode;
this.mErrMsg = mErrMsg;
try {
@@ -172,7 +191,7 @@ public class PortfolioModel implements BraveWalletServiceObserverImplDelegate {
}
}
public Erc721MetaData(String mDescription, String mImageUrl, String mName) {
public NftMetadata(String mDescription, String mImageUrl, String mName) {
this.mDescription = mDescription;
this.mImageUrl = mImageUrl;
this.mName = mName;
@@ -28,10 +28,12 @@ import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.request.transition.DrawableCrossFadeTransition;
import org.chromium.brave_wallet.mojom.BlockchainToken;
import org.chromium.brave_wallet.mojom.CoinType;
import org.chromium.brave_wallet.mojom.NetworkInfo;
import org.chromium.chrome.R;
import org.chromium.chrome.browser.app.BraveActivity;
import org.chromium.chrome.browser.app.domain.PortfolioModel;
import org.chromium.chrome.browser.crypto_wallet.util.AddressUtils;
import org.chromium.chrome.browser.crypto_wallet.util.AndroidUtils;
import org.chromium.chrome.browser.crypto_wallet.util.ImageLoader;
import org.chromium.chrome.browser.crypto_wallet.util.Utils;
@@ -39,15 +41,22 @@ import org.chromium.chrome.browser.util.LiveDataUtil;
import org.chromium.chrome.browser.util.TabUtils;
import org.chromium.ui.text.NoUnderlineClickableSpan;
import java.util.Locale;
public class NftDetailActivity extends BraveWalletBaseActivity {
private static final String TOKEN_ID_FORMAT = "#%s";
private static final String NFT_URL_FORMAT = "%s/token/%s?a=%s";
private static final String NFT_ERC721_URL_FORMAT = "%s/token/%s?a=%s";
private static final String NFT_SPL_URL_FORMAT = "%s/address/%s";
private static final String NFT_SPL_URL_FORMAT_WITH_CLUSTER =
NFT_SPL_URL_FORMAT + "/?cluster=%s";
private static final String CHAIN_ID = "chainId";
private static final String ASSET_NAME = "assetName";
private static final String ASSET_CONTRACT_ADDRESS = "assetContractAddress";
private static final String NFT_TOKEN_ID_HEX = "nftTokenIdHex";
private static final String NFT_META_DATA = "nftMetaData";
private static final String NFT_META_DATA = "nftMetadata";
private static final String NFT_IS_ERC_721 = "nftIsErc721";
private static final String COIN_TYPE = "coinType";
private String mNftName;
private String mChainId;
@@ -55,19 +64,26 @@ public class NftDetailActivity extends BraveWalletBaseActivity {
private String mNftTokenId;
private String mNftTokenHex;
private boolean mIsErc721;
private int mCoinType;
private ImageView mNftImageView;
private TextView mImageNotAvailableText;
private TextView mNetworkNameView;
private Button mBtnSend;
private TextView mTokenStandardView;
private TextView mTokenIdView;
private TextView mTokenAddressLabelView;
private TextView mTokenAddressView;
private TextView mDescriptionContentView;
private TextView mNftDetailTitleView;
private TextView mNftNameView;
private ViewGroup mNftDescriptionLayout;
private ViewGroup mNftTokenStandardLayout;
private ViewGroup mNftTokenAddressLayout;
private Toolbar mToolbar;
private PortfolioModel.Erc721MetaData mErc721MetaData;
private PortfolioModel.NftMetadata mNftMetadata;
@Override
protected void triggerLayoutInflation() {
@@ -80,8 +96,9 @@ public class NftDetailActivity extends BraveWalletBaseActivity {
mContractAddress = intent.getStringExtra(ASSET_CONTRACT_ADDRESS);
mNftTokenHex = intent.getStringExtra(NFT_TOKEN_ID_HEX);
mNftTokenId = Utils.hexToIntString(mNftTokenHex);
mErc721MetaData =
(PortfolioModel.Erc721MetaData) intent.getSerializableExtra(NFT_META_DATA);
mNftMetadata = (PortfolioModel.NftMetadata) intent.getSerializableExtra(NFT_META_DATA);
mIsErc721 = intent.getBooleanExtra(NFT_IS_ERC_721, false);
mCoinType = intent.getIntExtra(COIN_TYPE, -1);
}
// Calculate half screen height and assign it to NFT image view,
@@ -102,6 +119,7 @@ public class NftDetailActivity extends BraveWalletBaseActivity {
mBtnSend = findViewById(R.id.btn_send);
// TODO(simone): Enable if it's the NFT owner.
// GitHub issue: https://github.com/brave/brave-browser/issues/27802.
mBtnSend.setVisibility(View.GONE);
mNftDetailTitleView = findViewById(R.id.nft_detail_title);
@@ -111,15 +129,28 @@ public class NftDetailActivity extends BraveWalletBaseActivity {
mNftNameView.setText(mNftName);
mNetworkNameView = findViewById(R.id.blockchain_content);
mNftTokenStandardLayout = findViewById(R.id.nft_token_standard);
mNftTokenAddressLayout = findViewById(R.id.nft_token_address);
mTokenStandardView = findViewById(R.id.token_standard_content);
mTokenStandardView.setText(R.string.brave_wallet_nft_erc_721);
mTokenAddressLabelView = findViewById(R.id.token_address_label);
mTokenIdView = findViewById(R.id.token_id_content);
if (mIsErc721) {
mTokenStandardView.setText(R.string.brave_wallet_nft_erc_721);
mTokenAddressLabelView.setText(R.string.brave_wallet_nft_token_id);
} else if (mCoinType == CoinType.SOL) {
mTokenStandardView.setText(R.string.brave_wallet_nft_sol_spl);
mTokenAddressLabelView.setText(R.string.brave_wallet_nft_mint_address);
} else {
// Not ERC 721, nor Solana NFT.
// Hiding incompatible lables.
AndroidUtils.gone(mNftTokenStandardLayout, mNftTokenAddressLayout);
}
mTokenAddressView = findViewById(R.id.token_address_content);
mNftDescriptionLayout = findViewById(R.id.nft_description);
setMetadata(mErc721MetaData);
setMetadata(mNftMetadata);
BraveActivity braveActivity = BraveActivity.getBraveActivity();
assert braveActivity != null;
@@ -133,31 +164,60 @@ public class NftDetailActivity extends BraveWalletBaseActivity {
? defaultNetwork.blockExplorerUrls[0]
: "";
String tokenStr = String.format(TOKEN_ID_FORMAT, mNftTokenId);
SpannableString spannable = new SpannableString(tokenStr);
SpannableString spannable;
if (mIsErc721) {
String tokenStr =
String.format(Locale.ENGLISH, TOKEN_ID_FORMAT, mNftTokenId);
spannable = new SpannableString(tokenStr);
if (!TextUtils.isEmpty(blockExplorerUrl)) {
String url = String.format(
NFT_URL_FORMAT, blockExplorerUrl, mContractAddress, mNftTokenId);
NoUnderlineClickableSpan linkSpan =
new NoUnderlineClickableSpan(this, R.color.brave_link,
(textView) -> { TabUtils.openLinkWithFocus(this, url); });
spannable.setSpan(
linkSpan, 0, spannable.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
if (!TextUtils.isEmpty(blockExplorerUrl)) {
String url = String.format(Locale.ENGLISH, NFT_ERC721_URL_FORMAT,
blockExplorerUrl, mContractAddress, mNftTokenId);
createClickableLink(blockExplorerUrl, url, spannable);
}
} else {
String mintAddress = AddressUtils.getTruncatedAddress(mContractAddress);
spannable = new SpannableString(mintAddress);
if (!TextUtils.isEmpty(blockExplorerUrl)) {
// Blockchain explorer URLs may contain a cluster endpoint.
// When present it must be appended at the end of the formatted URL.
String[] splitBlockExplorerUrl = blockExplorerUrl.split("/\\?cluster=");
String baseUrl = splitBlockExplorerUrl[0];
String url;
if (splitBlockExplorerUrl.length > 1) {
String cluster = splitBlockExplorerUrl[1];
url = String.format(Locale.ENGLISH, NFT_SPL_URL_FORMAT_WITH_CLUSTER,
baseUrl, mContractAddress, cluster);
} else {
url = String.format(Locale.ENGLISH, NFT_SPL_URL_FORMAT, baseUrl,
mContractAddress);
}
createClickableLink(blockExplorerUrl, url, spannable);
}
}
mTokenIdView.setText(spannable);
mTokenIdView.setMovementMethod(LinkMovementMethod.getInstance());
mTokenAddressView.setText(spannable);
mTokenAddressView.setMovementMethod(LinkMovementMethod.getInstance());
onInitialLayoutInflationComplete();
});
}
private void setMetadata(PortfolioModel.Erc721MetaData erc721MetaData) {
if (erc721MetaData == null) return;
private void createClickableLink(
String blockExplorerUrl, String url, SpannableString spannable) {
NoUnderlineClickableSpan linkSpan = new NoUnderlineClickableSpan(
this, R.color.brave_link, (textView) -> { TabUtils.openLinkWithFocus(this, url); });
spannable.setSpan(linkSpan, 0, spannable.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}
private void setMetadata(PortfolioModel.NftMetadata nftMetadata) {
if (nftMetadata == null) return;
// In case of no errors proceed to assign description and fetch NFT image.
if (erc721MetaData.mErrCode == 0) {
String description = erc721MetaData.mDescription;
String imageUrl = erc721MetaData.mImageUrl;
if (nftMetadata.mErrCode == 0) {
String description = nftMetadata.mDescription;
String imageUrl = nftMetadata.mImageUrl;
if (!TextUtils.isEmpty(description)) {
mDescriptionContentView.setText(description);
} else {
@@ -206,7 +266,9 @@ public class NftDetailActivity extends BraveWalletBaseActivity {
intent.putExtra(ASSET_NAME, asset.name);
intent.putExtra(ASSET_CONTRACT_ADDRESS, asset.contractAddress);
intent.putExtra(NFT_TOKEN_ID_HEX, asset.tokenId);
intent.putExtra(NFT_META_DATA, nftDataModel.erc721MetaData);
intent.putExtra(NFT_META_DATA, nftDataModel.nftMetadata);
intent.putExtra(NFT_IS_ERC_721, nftDataModel.token.isErc721);
intent.putExtra(COIN_TYPE, asset.coin);
return intent;
}
}
@@ -1,7 +1,7 @@
/* Copyright (c) 2021 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
* You can obtain one at https://mozilla.org/MPL/2.0/. */
package org.chromium.chrome.browser.crypto_wallet.adapters;
@@ -196,8 +196,8 @@ public class WalletCoinAdapter extends RecyclerView.Adapter<WalletCoinAdapter.Vi
} else {
PortfolioModel.NftDataModel nftDataModel = walletListItemModel.getNftDataModel();
if (walletListItemModel.hasNftImageLink()
&& ImageLoader.isSupported(nftDataModel.erc721MetaData.mImageUrl)) {
String url = nftDataModel.erc721MetaData.mImageUrl;
&& ImageLoader.isSupported(nftDataModel.nftMetadata.mImageUrl)) {
String url = nftDataModel.nftMetadata.mImageUrl;
ImageLoader.createLoadNftRequest(url, context, false).into(holder.iconImg);
} else {
Utils.setBlockiesBitmapCustomAsset(mExecutor, mHandler, holder.iconImg,
@@ -59,8 +59,8 @@ public class WalletListItemModel {
}
public boolean hasNftImageLink() {
return isNft() && mNftDataModel != null && mNftDataModel.erc721MetaData != null
&& !TextUtils.isEmpty(mNftDataModel.erc721MetaData.mImageUrl);
return isNft() && mNftDataModel != null && mNftDataModel.nftMetadata != null
&& !TextUtils.isEmpty(mNftDataModel.nftMetadata.mImageUrl);
}
public void setTransactionInfo(TransactionInfo txInfo) {
@@ -1,7 +1,7 @@
/* Copyright (c) 2022 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
* You can obtain one at https://mozilla.org/MPL/2.0/. */
package org.chromium.chrome.browser.crypto_wallet.util;
@@ -15,4 +15,14 @@ public class AddressUtils {
}
return ethAddress;
}
/**
* Gets truncated address from a full contract address.
* @param address full contract address
* @return truncated address
*/
public static String getTruncatedAddress(String address) {
int prefixLength = address.startsWith("0x") ? 6 : 4;
return (address.substring(0, prefixLength) + "" + address.substring(address.length() - 4));
}
}
@@ -390,20 +390,41 @@ public class AsyncUtils {
}
}
public static class GetNftMetaDataContext
extends SingleResponseBaseContext implements JsonRpcService.GetErc721Metadata_Response {
public static abstract class BaseGetNftMetadataContext extends SingleResponseBaseContext {
public BlockchainToken asset;
public String erc721Metadata;
public String tokenMetadata;
public Integer errorCode;
public String errorMessage;
public GetNftMetaDataContext(Runnable responseCompleteCallback) {
public BaseGetNftMetadataContext(Runnable responseCompleteCallback) {
super(responseCompleteCallback);
}
}
public static class GetNftSolanaMetadataContext extends BaseGetNftMetadataContext
implements JsonRpcService.GetSolTokenMetadata_Response {
public GetNftSolanaMetadataContext(Runnable responseCompleteCallback) {
super(responseCompleteCallback);
}
@Override
public void call(String tokenMetadata, Integer errorCode, String errorMessage) {
this.tokenMetadata = tokenMetadata;
this.errorCode = errorCode;
this.errorMessage = errorMessage;
super.fireResponseCompleteCallback();
}
}
public static class GetNftErc721MetadataContext
extends BaseGetNftMetadataContext implements JsonRpcService.GetErc721Metadata_Response {
public GetNftErc721MetadataContext(Runnable responseCompleteCallback) {
super(responseCompleteCallback);
}
@Override
public void call(String erc721Metadata, Integer errorCode, String errorMessage) {
this.erc721Metadata = erc721Metadata;
this.tokenMetadata = erc721Metadata;
this.errorCode = errorCode;
this.errorMessage = errorMessage;
super.fireResponseCompleteCallback();
@@ -1,7 +1,7 @@
/* Copyright (c) 2021 The Brave Authors. All rights reserved.
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
* You can obtain one at http://mozilla.org/MPL/2.0/. */
* You can obtain one at https://mozilla.org/MPL/2.0/. */
package org.chromium.chrome.browser.crypto_wallet.util;
@@ -57,6 +57,7 @@
<TextView
android:id="@+id/nft_detail_image_not_available"
style="@style/BraveWalletTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
@@ -65,7 +66,7 @@
android:gravity="center"
android:text="@string/brave_wallet_nft_image_not_available"
android:textColor="@color/wallet_secondary_text_color"
android:textSize="14sp"
android:textSize="16sp"
android:visibility="gone"/>
<TextView
@@ -80,10 +81,11 @@
<TextView
android:id="@+id/nft_name"
style="@style/BraveWalletTextView"
style="@style/BraveWalletTextViewSubTitle"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:layout_marginBottom="16dp"
android:textSize="16sp"
android:textColor="@color/wallet_secondary_text_color" />
<androidx.appcompat.widget.AppCompatButton
@@ -97,7 +99,7 @@
android:background="@drawable/crypto_wallet_blue_button"
android:text="@string/send"
android:textAllCaps="false"
android:textSize="16sp"
android:textSize="17sp"
android:paddingTop="8dp"
android:paddingBottom="8dp"
android:paddingStart="16dp"
@@ -121,6 +123,7 @@
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:text="@string/brave_wallet_nft_description"
android:textSize="16sp"
android:textColor="@color/wallet_secondary_text_color"
android:textStyle="bold" />
<TextView
@@ -128,6 +131,7 @@
style="@style/BraveWalletTextView"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textSize="16sp"
android:textColor="@color/wallet_secondary_text_color" />
</LinearLayout>
@@ -144,6 +148,7 @@
style="@style/BraveWalletTextView"
android:text="@string/brave_wallet_nft_blockchain"
android:textColor="@color/wallet_secondary_text_color"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/blockchain_content"
@@ -151,6 +156,7 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/wallet_secondary_text_color"
android:textSize="16sp"
android:gravity="end" />
</LinearLayout>
@@ -167,6 +173,7 @@
style="@style/BraveWalletTextView"
android:text="@string/brave_wallet_nft_token_standard"
android:textColor="@color/wallet_secondary_text_color"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/token_standard_content"
@@ -174,11 +181,12 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/wallet_secondary_text_color"
android:textSize="16sp"
android:gravity="end" />
</LinearLayout>
<LinearLayout
android:id="@+id/nft_token_id"
android:id="@+id/nft_token_address"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="16dp"
@@ -186,17 +194,18 @@
android:layout_marginBottom="8dp"
android:orientation="horizontal">
<TextView
android:id="@+id/token_id_label"
android:id="@+id/token_address_label"
style="@style/BraveWalletTextView"
android:text="@string/brave_wallet_nft_token_id"
android:textColor="@color/wallet_secondary_text_color"
android:textSize="16sp"
android:textStyle="bold" />
<TextView
android:id="@+id/token_id_content"
android:id="@+id/token_address_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/wallet_secondary_text_color"
android:textSize="14sp"
android:textSize="16sp"
android:gravity="end"
/>
</LinearLayout>
@@ -2055,12 +2055,18 @@ Are you sure you want to do this?
<message name="IDS_BRAVE_WALLET_NFT_TOKEN_STANDARD" desc="Token standard text for Brave wallet NFT asset detail.">
Token Standard
</message>
<message name="IDS_BRAVE_WALLET_NFT_ERC_721" desc="Type of NFT token standard. It does not need to be translated.">
<message name="IDS_BRAVE_WALLET_NFT_ERC_721" desc="Type of NFT token standard." translateable="false">
ERC 721
</message>
<message name="IDS_BRAVE_WALLET_NFT_SOL_SPL" desc="Type of NFT token standard adopted by Solana." translateable="false">
SPL
</message>
<message name="IDS_BRAVE_WALLET_NFT_TOKEN_ID" desc="Token ID text for brave wallet NFT asset detail.">
Token ID
</message>
<message name="IDS_BRAVE_WALLET_NFT_MINT_ADDRESS" desc="Mint address text for brave wallet NFT asset detail.">
Mint Address
</message>
<message name="IDS_BRAVE_WALLET_NFT_IMAGE_NOT_AVAILABLE" desc="Text message for image not available when loading NFT image.">
Image not available
</message>