From 1f9c7c8fc83ca120277d6d0df17b8a13b556e0ea Mon Sep 17 00:00:00 2001 From: James Brown Date: Sun, 3 Sep 2023 20:39:13 +1000 Subject: [PATCH] Implement TokenScript view for Attestations (#3284) --- app/build.gradle | 1 + app/src/debug/AndroidManifest.xml | 4 +- .../entity/attestation/AttestationImport.java | 12 +- .../app/entity/tokens/Attestation.java | 62 +- .../app/entity/tokens/TokenFactory.java | 10 +- .../app/ui/AssetDisplayActivity.java | 2 +- .../app/ui/NFTAssetDetailActivity.java | 80 +- .../com/alphawallet/app/ui/TokenActivity.java | 912 ------------------ .../app/ui/widget/holder/TokenHolder.java | 2 +- .../app/viewmodel/HomeViewModel.java | 7 +- .../alphawallet/app/web3/Web3TokenView.java | 4 +- .../res/layout/activity_nft_asset_detail.xml | 22 +- app/src/main/res/values-es/strings.xml | 12 +- app/src/main/res/values-fr/strings.xml | 4 +- app/src/main/res/values-zh/strings.xml | 2 +- .../token/entity/AttestationDefinition.java | 53 +- .../token/entity/TSTokenViewHolder.java | 13 +- .../token/tools/TokenDefinition.java | 7 +- .../token/tools/XMLDSigVerifier.java | 42 +- .../Ethereum/TransactionHandler.java | 18 +- 20 files changed, 210 insertions(+), 1059 deletions(-) delete mode 100644 app/src/main/java/com/alphawallet/app/ui/TokenActivity.java diff --git a/app/build.gradle b/app/build.gradle index 08c37eee2..68cd30299 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -170,6 +170,7 @@ android { } release { minifyEnabled false + shrinkResources false if (signingConfigs.release.storeFile) { signingConfig signingConfigs.release } diff --git a/app/src/debug/AndroidManifest.xml b/app/src/debug/AndroidManifest.xml index a8c260563..6715d269e 100644 --- a/app/src/debug/AndroidManifest.xml +++ b/app/src/debug/AndroidManifest.xml @@ -18,7 +18,6 @@ - + diff --git a/app/src/main/java/com/alphawallet/app/entity/attestation/AttestationImport.java b/app/src/main/java/com/alphawallet/app/entity/attestation/AttestationImport.java index f060c01a1..640929b31 100644 --- a/app/src/main/java/com/alphawallet/app/entity/attestation/AttestationImport.java +++ b/app/src/main/java/com/alphawallet/app/entity/attestation/AttestationImport.java @@ -286,9 +286,8 @@ public class AttestationImport private void completeImport(Token token) { - if (token instanceof Attestation && ((Attestation)token).isValid() == AttestationValidationStatus.Pass) + if (token instanceof Attestation tokenAttn && ((Attestation)token).isValid() == AttestationValidationStatus.Pass) { - Attestation tokenAttn = (Attestation)token; TokenCardMeta tcmAttestation = new TokenCardMeta(tokenAttn.tokenInfo.chainId, tokenAttn.getAddress(), "1", System.currentTimeMillis(), assetDefinitionService, tokenAttn.tokenInfo.name, tokenAttn.tokenInfo.symbol, tokenAttn.getBaseTokenType(), TokenGroup.ATTESTATION, tokenAttn.getAttestationUID()); @@ -370,14 +369,7 @@ public class AttestationImport String collectionHash = localAttestation.getAttestationCollectionId(); //is it a smartpass? - if (localAttestation.hasSmartPassElement() && issuerOnKeyChain) - { - tInfo = Attestation.getSmartPassInfo(attestation.getChainId(), collectionHash); - } - else - { - tInfo = Attestation.getDefaultAttestationInfo(attestation.getChainId(), collectionHash); - } + tInfo = Attestation.getDefaultAttestationInfo(attestation.getChainId(), collectionHash); //Now regenerate with the correct collectionId localAttestation = new Attestation(tInfo, networkInfo.name, originLink.getBytes(StandardCharsets.UTF_8)); diff --git a/app/src/main/java/com/alphawallet/app/entity/tokens/Attestation.java b/app/src/main/java/com/alphawallet/app/entity/tokens/Attestation.java index 5eb412aaf..11a1a996a 100644 --- a/app/src/main/java/com/alphawallet/app/entity/tokens/Attestation.java +++ b/app/src/main/java/com/alphawallet/app/entity/tokens/Attestation.java @@ -61,13 +61,13 @@ public class Attestation extends Token private static final String VALID_TO = "expirationTime"; private static final String TICKET_ID = "TicketId"; private static final String SCRIPT_URI = "scriptURI"; - private static final String EVENT_ID = "orgId"; + private static final String EVENT_IDS = "orgId,eventId,devconId"; //TODO: Remove once we use TokenScript + private static final String SECONDARY_IDS = "version"; private static final String SCHEMA_DATA_PREFIX = "data."; public static final String ATTESTATION_SUFFIX = "-att"; public static final String EAS_ATTESTATION_TEXT = "EAS Attestation"; public static final String EAS_ATTESTATION_SYMBOL = "ATTN"; - public static final String SMART_PASS = "Smartpass"; - private static final String SMARTLAYER = "SL";//"SMARTLAYER"; + private static final String SMART_LAYER = "SMARTLAYER"; //TODO: Remove once we use TokenScript //TODO: Supplemental data public Attestation(TokenInfo tokenInfo, String networkName, byte[] attestation) @@ -82,11 +82,6 @@ public class Attestation extends Token return new TokenInfo(collectionHash, EAS_ATTESTATION_TEXT, EAS_ATTESTATION_SYMBOL, 0, true, chainId); } - public static TokenInfo getSmartPassInfo(long chainId, String collectionHash) - { - return new TokenInfo(collectionHash, SMART_PASS, EAS_ATTESTATION_SYMBOL, 0, true, chainId); - } - public byte[] getAttestation() { return attestation; @@ -177,12 +172,9 @@ public class Attestation extends Token @Override public String getAttestationCollectionId() { - String eventId = null; - MemberData eventMember = additionalMembers.get(SCHEMA_DATA_PREFIX + EVENT_ID); - if (eventMember != null) - { - eventId = eventMember.getString(); - } + // TODO: Pull these from the TokenScript + String eventId = getCollectionId(EVENT_IDS); + String eventPostFix = getCollectionId(SECONDARY_IDS); EasAttestation easAttestation = getEasAttestation(); @@ -196,7 +188,7 @@ public class Attestation extends Token //issuer public key //calculate hash from attestation String hexStr = Numeric.cleanHexPrefix(easAttestation.getSchema()).toLowerCase(Locale.ROOT) + Keys.getAddress(recoverPublicKey(easAttestation)).toLowerCase(Locale.ROOT) - + (!TextUtils.isEmpty(eventId) ? eventId : ""); + + eventId + eventPostFix; //now convert this into ASCII hex bytes collectionBytes = hexStr.getBytes(StandardCharsets.UTF_8); } @@ -207,6 +199,23 @@ public class Attestation extends Token return Numeric.toHexString(hash); } + private String getCollectionId(String eventIds) + { + String collectionId = ""; + String[] candidates = eventIds.split(","); + for (String candidate : candidates) + { + MemberData memberData = additionalMembers.get(SCHEMA_DATA_PREFIX + candidate); + if (memberData != null) + { + collectionId = memberData.getString(); + break; + } + } + + return collectionId; + } + @Override public String getTSKey() { @@ -470,28 +479,20 @@ public class Attestation extends Token } } - public static boolean hasSmartPassElementCheck(String jsonData) + public boolean knownIssuerKey() { - return hasSmartPassElementInternal(getMembersFromJSON(jsonData)); + return getKnownRootIssuers(tokenInfo.chainId).contains(issuerKey); } - public boolean hasSmartPassElement() - { - return hasSmartPassElementInternal(additionalMembers); - } - - private static boolean hasSmartPassElementInternal(Map members) + public boolean isSmartPass() { - MemberData orgId = members.getOrDefault("data.orgId", null); - String orgIdValue = orgId != null ? orgId.getString() : ""; - return orgIdValue.equalsIgnoreCase(SMARTLAYER); + //TODO: This should use TokenScript + return knownIssuerKey() && orgIsSmartLayer(); } - public boolean isSmartPass() + private boolean orgIsSmartLayer() { - //find SMARTPASS in the schema elements and check against correct issuer. TODO: check against keychain if not valid - boolean issuerMatch = getKnownRootIssuers(tokenInfo.chainId).contains(issuerKey); - return hasSmartPassElementInternal(additionalMembers) && issuerMatch; + return getCollectionId(EVENT_IDS).equalsIgnoreCase(SMART_LAYER); } private static class MemberData @@ -769,6 +770,7 @@ public class Attestation extends Token BigInteger key = Sign.signedMessageHashToKey(hash, sig); recoveredKey = Numeric.toHexString(Numeric.toBytesPadded(key, 64)); + Timber.i("Public Key: %s", recoveredKey); } catch (Exception e) { diff --git a/app/src/main/java/com/alphawallet/app/entity/tokens/TokenFactory.java b/app/src/main/java/com/alphawallet/app/entity/tokens/TokenFactory.java index 9c08f5bc1..c8b7e8209 100644 --- a/app/src/main/java/com/alphawallet/app/entity/tokens/TokenFactory.java +++ b/app/src/main/java/com/alphawallet/app/entity/tokens/TokenFactory.java @@ -218,8 +218,8 @@ public class TokenFactory { EasAttestation easAttn = new Gson().fromJson(jsonAttestation, EasAttestation.class); String recoverAttestationSigner = AttestationImport.recoverSigner(easAttn); - TokenInfo tInfo = createAttestationTokenInfo(token, info, recoverAttestationSigner, - rAttn.getTokenAddress(), Attestation.hasSmartPassElementCheck(rAttn.getSubTitle())); + TokenInfo tInfo = createAttestationTokenInfo(token, info, + rAttn.getTokenAddress()); Attestation attn = new Attestation(tInfo, info.name, rAttn.getAttestationLink().getBytes(StandardCharsets.UTF_8)); attn.setTokenWallet(wallet); attn.loadAttestationData(rAttn, recoverAttestationSigner); @@ -227,17 +227,13 @@ public class TokenFactory } } - private TokenInfo createAttestationTokenInfo(Token token, NetworkInfo info, String recoverAttestationSigner, String tokenAddress, boolean hasSmartPassElement) + private TokenInfo createAttestationTokenInfo(Token token, NetworkInfo info, String tokenAddress) { TokenInfo tInfo; if (token != null) { tInfo = token.tokenInfo; } - else if (hasSmartPassElement && Attestation.getKnownRootIssuers(info.chainId).contains(recoverAttestationSigner)) - { - tInfo = Attestation.getSmartPassInfo(info.chainId, tokenAddress); - } else { tInfo = Attestation.getDefaultAttestationInfo(info.chainId, tokenAddress); diff --git a/app/src/main/java/com/alphawallet/app/ui/AssetDisplayActivity.java b/app/src/main/java/com/alphawallet/app/ui/AssetDisplayActivity.java index 31ff8f4fc..5747ba8c7 100644 --- a/app/src/main/java/com/alphawallet/app/ui/AssetDisplayActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/AssetDisplayActivity.java @@ -211,7 +211,7 @@ public class AssetDisplayActivity extends BaseActivity implements StandardFuncti { BigInteger tokenId = token.getArrayBalance().get(0); TicketRange data = new TicketRange(tokenId, token.getAddress()); - testView.renderTokenscriptView(token, data, viewModel.getAssetDefinitionService(), ViewType.ITEM_VIEW); + testView.renderTokenScriptView(token, data, viewModel.getAssetDefinitionService(), ViewType.ITEM_VIEW); testView.setOnReadyCallback(this); } else diff --git a/app/src/main/java/com/alphawallet/app/ui/NFTAssetDetailActivity.java b/app/src/main/java/com/alphawallet/app/ui/NFTAssetDetailActivity.java index 2718e3f9d..c8dce00db 100644 --- a/app/src/main/java/com/alphawallet/app/ui/NFTAssetDetailActivity.java +++ b/app/src/main/java/com/alphawallet/app/ui/NFTAssetDetailActivity.java @@ -3,7 +3,6 @@ package com.alphawallet.app.ui; import static android.text.Html.FROM_HTML_MODE_LEGACY; import static com.alphawallet.app.widget.AWalletAlertDialog.ERROR; import static com.alphawallet.app.widget.AWalletAlertDialog.WARNING; - import static java.util.Collections.singletonList; import android.content.Intent; @@ -16,6 +15,7 @@ import android.view.MenuItem; import android.view.View; import android.view.animation.Animation; import android.view.animation.AnimationUtils; +import android.widget.LinearLayout; import android.widget.ProgressBar; import android.widget.TextView; @@ -44,8 +44,9 @@ import com.alphawallet.app.service.GasService; import com.alphawallet.app.ui.widget.entity.ActionSheetCallback; import com.alphawallet.app.ui.widget.entity.NFTAttributeLayout; import com.alphawallet.app.util.ShortcutUtils; -import com.alphawallet.app.util.Utils; import com.alphawallet.app.viewmodel.TokenFunctionViewModel; +import com.alphawallet.app.web3.Web3TokenView; +import com.alphawallet.app.web3.entity.Address; import com.alphawallet.app.web3.entity.Web3Transaction; import com.alphawallet.app.widget.AWalletAlertDialog; import com.alphawallet.app.widget.ActionSheetDialog; @@ -57,15 +58,16 @@ import com.alphawallet.app.widget.TokenInfoView; import com.alphawallet.ethereum.EthereumNetworkBase; import com.alphawallet.hardware.SignatureFromKey; import com.alphawallet.token.entity.TSAction; +import com.alphawallet.token.entity.TicketRange; import com.alphawallet.token.entity.TokenScriptResult; import com.alphawallet.token.entity.TokenScriptResult.Attribute; +import com.alphawallet.token.entity.ViewType; import com.alphawallet.token.entity.XMLDsigDescriptor; import com.alphawallet.token.tools.TokenDefinition; import java.math.BigDecimal; import java.math.BigInteger; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Map; @@ -77,7 +79,6 @@ import io.reactivex.functions.Consumer; import io.reactivex.schedulers.Schedulers; import timber.log.Timber; - @AndroidEntryPoint public class NFTAssetDetailActivity extends BaseActivity implements StandardFunctionInterface, ActionSheetCallback { @@ -117,8 +118,6 @@ public class NFTAssetDetailActivity extends BaseActivity implements StandardFunc private long chainId; private Disposable disposable; - private boolean loadingInProgress; - @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -132,8 +131,6 @@ public class NFTAssetDetailActivity extends BaseActivity implements StandardFunc initIntents(); initViewModel(); - - getIntentData(); } private void initIntents() @@ -163,13 +160,9 @@ public class NFTAssetDetailActivity extends BaseActivity implements StandardFunc super.onResume(); if (viewModel != null) { - if (!loadingInProgress) - { - progressBar.setVisibility(View.VISIBLE); - viewModel.prepare(); - getIntentData(); - tokenImage.onResume(); - } + progressBar.setVisibility(View.VISIBLE); + getIntentData(); + tokenImage.onResume(); } else { @@ -194,7 +187,6 @@ public class NFTAssetDetailActivity extends BaseActivity implements StandardFunc { super.onPause(); tokenImage.onPause(); - loadingInProgress = false; } @Override @@ -253,8 +245,7 @@ public class NFTAssetDetailActivity extends BaseActivity implements StandardFunc sequenceId = getIntent().getStringExtra(C.EXTRA_STATE); if (C.ACTION_TOKEN_SHORTCUT.equals(getIntent().getAction())) { - loadingInProgress = true; - disposable = viewModel.findActiveWallet().subscribe(this::onActiveWalletFetched); + handleShortCut(); } else { @@ -279,7 +270,7 @@ public class NFTAssetDetailActivity extends BaseActivity implements StandardFunc } } - private void onActiveWalletFetched(Wallet activeWallet) + private void handleShortCut() { String walletAddress = getIntent().getStringExtra(C.Key.WALLET); viewModel.loadWallet(walletAddress); @@ -296,23 +287,8 @@ public class NFTAssetDetailActivity extends BaseActivity implements StandardFunc } } - private void showWarnDialog(String walletAddress) - { - AWalletAlertDialog alertDialog = new AWalletAlertDialog(this); - alertDialog.setIcon(WARNING); - alertDialog.setMessage(getApplicationContext().getString(R.string.warn_asset_not_belongs_to_active_wallet, Utils.formatAddress(walletAddress))); - alertDialog.setButton(R.string.yes_continue, v -> alertDialog.dismiss()); - alertDialog.setSecondaryButton(R.string.dialog_cancel_back, view -> { - alertDialog.dismiss(); - finish(); - }); - alertDialog.setCanceledOnTouchOutside(false); - alertDialog.show(); - } - private void setup() { - loadingInProgress = true; viewModel.checkForNewScript(token); viewModel.checkTokenScriptValidity(token); setTitle(token.tokenInfo.name); @@ -499,7 +475,6 @@ public class NFTAssetDetailActivity extends BaseActivity implements StandardFunc loadFromOpenSeaData(loadedAsset.getOpenSeaAsset()); completeTokenScriptSetup(); - loadingInProgress = false; } } @@ -635,7 +610,10 @@ public class NFTAssetDetailActivity extends BaseActivity implements StandardFunc { attnAsset.setupScriptElements(td); attnAsset.setupScriptAttributes(td, token); - tokenImage.setupTokenImage(attnAsset); + if (!displayTokenView(td)) + { + tokenImage.setupTokenImage(attnAsset); + } setTitle(attnAsset.getName()); if (!TextUtils.isEmpty(attnAsset.getDescription())) { @@ -845,4 +823,34 @@ public class NFTAssetDetailActivity extends BaseActivity implements StandardFunc { return viewModel.getWallet().type; } + + /*** + * TokenScript view handling + */ + private boolean displayTokenView(TokenDefinition td) + { + if (!td.hasTokenView()) + { + return false; + } + + try + { + //Attempt to display the token-view + Web3TokenView scriptView = findViewById(R.id.web3_tokenview); + LinearLayout webWrapper = findViewById(R.id.layout_webwrapper); + webWrapper.setVisibility(View.VISIBLE); + scriptView.setChainId(token.tokenInfo.chainId); + scriptView.setWalletAddress(new Address(token.getWallet())); + + scriptView.renderTokenScriptView(token, new TicketRange(BigInteger.ONE, token.getAddress()), viewModel.getAssetDefinitionService(), ViewType.VIEW); + } + catch (Exception e) + { + //fillEmpty(); + return false; + } + + return true; + } } diff --git a/app/src/main/java/com/alphawallet/app/ui/TokenActivity.java b/app/src/main/java/com/alphawallet/app/ui/TokenActivity.java deleted file mode 100644 index 6b8c7fa7a..000000000 --- a/app/src/main/java/com/alphawallet/app/ui/TokenActivity.java +++ /dev/null @@ -1,912 +0,0 @@ -package com.alphawallet.app.ui; - -import static com.alphawallet.app.C.ETH_SYMBOL; -import static com.alphawallet.app.entity.TransactionDecoder.FUNCTION_LENGTH; -import static com.alphawallet.app.service.AssetDefinitionService.ASSET_DETAIL_VIEW_NAME; -import static com.alphawallet.app.ui.widget.holder.TransactionHolder.TRANSACTION_BALANCE_PRECISION; -import static com.alphawallet.app.widget.AWalletAlertDialog.ERROR; -import static com.alphawallet.ethereum.EthereumNetworkBase.MAINNET_ID; - -import android.app.Activity; -import android.content.Intent; -import android.os.Bundle; -import android.os.Handler; -import android.text.TextUtils; -import android.util.Base64; -import android.view.Menu; -import android.view.MenuItem; -import android.view.View; -import android.webkit.WebView; -import android.widget.LinearLayout; -import android.widget.TextView; - -import androidx.activity.result.ActivityResult; -import androidx.activity.result.ActivityResultCallback; -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.annotation.Nullable; -import androidx.lifecycle.ViewModelProvider; - -import com.alphawallet.app.C; -import com.alphawallet.app.R; -import com.alphawallet.app.entity.ContractType; -import com.alphawallet.app.entity.SignAuthenticationCallback; -import com.alphawallet.app.entity.StandardFunctionInterface; -import com.alphawallet.app.entity.Transaction; -import com.alphawallet.app.entity.TransactionReturn; -import com.alphawallet.app.entity.TransactionType; -import com.alphawallet.app.entity.Wallet; -import com.alphawallet.app.entity.WalletType; -import com.alphawallet.app.entity.nftassets.NFTAsset; -import com.alphawallet.app.entity.tokens.Token; -import com.alphawallet.app.entity.tokenscript.TokenScriptRenderCallback; -import com.alphawallet.app.entity.tokenscript.WebCompletionCallback; -import com.alphawallet.app.repository.EventResult; -import com.alphawallet.app.repository.TransactionsRealmCache; -import com.alphawallet.app.repository.entity.RealmAuxData; -import com.alphawallet.app.repository.entity.RealmTransaction; -import com.alphawallet.app.router.TokenDetailRouter; -import com.alphawallet.app.service.AssetDefinitionService; -import com.alphawallet.app.ui.widget.entity.ActionSheetCallback; -import com.alphawallet.app.ui.widget.entity.TokenTransferData; -import com.alphawallet.app.util.BalanceUtils; -import com.alphawallet.app.util.KeyboardUtils; -import com.alphawallet.app.util.Utils; -import com.alphawallet.app.viewmodel.TokenFunctionViewModel; -import com.alphawallet.app.web3.OnSetValuesListener; -import com.alphawallet.app.web3.Web3TokenView; -import com.alphawallet.app.web3.entity.Address; -import com.alphawallet.app.web3.entity.PageReadyCallback; -import com.alphawallet.app.web3.entity.Web3Transaction; -import com.alphawallet.app.widget.AWalletAlertDialog; -import com.alphawallet.app.widget.ActionSheetDialog; -import com.alphawallet.app.entity.analytics.ActionSheetMode; -import com.alphawallet.app.widget.AmountDisplayWidget; -import com.alphawallet.app.widget.ChainName; -import com.alphawallet.app.widget.EventDetailWidget; -import com.alphawallet.app.widget.FunctionButtonBar; -import com.alphawallet.app.widget.TokenIcon; -import com.alphawallet.hardware.SignatureFromKey; -import com.alphawallet.token.entity.TSActivityView; -import com.alphawallet.token.entity.TSTokenView; -import com.alphawallet.token.entity.TokenScriptResult; -import com.alphawallet.token.tools.Numeric; -import com.alphawallet.token.tools.TokenDefinition; - -import org.jetbrains.annotations.NotNull; - -import java.math.BigDecimal; -import java.math.BigInteger; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.concurrent.TimeUnit; - -import dagger.hilt.android.AndroidEntryPoint; -import io.reactivex.Observable; -import io.reactivex.android.schedulers.AndroidSchedulers; -import io.reactivex.disposables.Disposable; -import io.reactivex.schedulers.Schedulers; -import io.realm.Realm; -import timber.log.Timber; - - -/** - * Created by JB on 6/08/2020. - */ -@AndroidEntryPoint -public class TokenActivity extends BaseActivity implements PageReadyCallback, StandardFunctionInterface, ActionSheetCallback, - TokenScriptRenderCallback, WebCompletionCallback, OnSetValuesListener -{ - private TokenFunctionViewModel viewModel; - - private TokenIcon icon; - private FunctionButtonBar functionBar; - private String eventKey; - private String transactionHash; - private Token token; - private StringBuilder attrs; - private Web3TokenView tokenView; - private EventDetailWidget eventDetail; - private int parsePass; - private final Map args = new HashMap<>(); - private TSTokenView scriptViewData; - private BigInteger tokenId; - private final Handler handler = new Handler(); - private boolean isFromTokenHistory = false; - private long pendingStart = 0; - private TokenTransferData transferData; - private ActionSheetDialog confirmationDialog; - private AWalletAlertDialog dialog; - - @Nullable - private Disposable pendingTxUpdate = null; - - @Nullable - private Disposable fetchMetadata = null; - private Wallet wallet; - - @Override - protected void onCreate(@Nullable Bundle savedInstanceState) - { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_token_activity); - - if (savedInstanceState != null && savedInstanceState.containsKey(C.EXTRA_ACTION_NAME)) - { - eventKey = savedInstanceState.getString(C.EXTRA_ACTION_NAME); - transactionHash = savedInstanceState.getString(C.EXTRA_TXHASH); - isFromTokenHistory = savedInstanceState.getBoolean(C.EXTRA_STATE, false); - transferData = savedInstanceState.getParcelable(C.EXTRA_TOKEN_ID); - } - else - { - eventKey = getIntent().getStringExtra(C.EXTRA_ACTION_NAME); - transactionHash = getIntent().getStringExtra(C.EXTRA_TXHASH); - isFromTokenHistory = getIntent().getBooleanExtra(C.EXTRA_STATE, false); - transferData = getIntent().getParcelableExtra(C.EXTRA_TOKEN_ID); - } - //TODO: Send event details - icon = findViewById(R.id.token_icon); - - toolbar(); - setTitle(getString(R.string.activity_label)); - - findViewById(R.id.layout_select_ticket).setVisibility(View.GONE); - - tokenId = BigInteger.ZERO; - eventDetail = findViewById(R.id.event_detail); - } - - private void setupViewModel() - { - viewModel = new ViewModelProvider(this) - .get(TokenFunctionViewModel.class); - viewModel.walletUpdate().observe(this, this::onWallet); - viewModel.transactionFinalised().observe(this, this::txWritten); - viewModel.transactionError().observe(this, this::txError); - } - - private void initViews() - { - tokenView = findViewById(R.id.web3_tokenview); - functionBar = findViewById(R.id.layoutButtons); - } - - @Override - public boolean onCreateOptionsMenu(Menu menu) { - getMenuInflater().inflate(R.menu.menu_activity, menu); - return super.onCreateOptionsMenu(menu); - } - - ActivityResultLauncher txDetailPage = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), - new ActivityResultCallback() { - @Override - public void onActivityResult(ActivityResult result) { - if (result.getData() != null && result.getData().hasExtra(C.EXTRA_TXHASH)) - { - transactionHash = result.getData().getStringExtra(C.EXTRA_TXHASH); - //onResume will be called by OS and the transaction will reset - } - } - }); - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == R.id.action_view_transaction_details) - { - Intent intent = new Intent(this, TransactionDetailActivity.class); - intent.putExtra(C.EXTRA_TXHASH, transactionHash); - intent.putExtra(C.EXTRA_CHAIN_ID, token.tokenInfo.chainId); - intent.putExtra(C.Key.WALLET, viewModel.getWallet()); - intent.putExtra(C.EXTRA_ADDRESS, token.getAddress()); - intent.setFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK); - txDetailPage.launch(intent); - } - return super.onOptionsItemSelected(item); - } - - @Override - public void onResume() - { - super.onResume(); - initViews(); - if (viewModel == null) - { - setupViewModel(); - } - - viewModel.getCurrentWallet(); - viewModel.restartServices(); - } - - @Override - public void onPause() - { - super.onPause(); - stopPendingUpdate(); - } - - @Override - public void onDestroy() - { - super.onDestroy(); - if (pendingTxUpdate != null && !pendingTxUpdate.isDisposed()) pendingTxUpdate.dispose(); - if (fetchMetadata != null && !fetchMetadata.isDisposed()) fetchMetadata.dispose(); - if (eventDetail != null) eventDetail.onDestroy(); - } - - private void txWritten(TransactionReturn transactionReturn) - { - confirmationDialog.transactionWritten(transactionReturn.hash); - transactionHash = transactionReturn.hash; - //reset display using new transaction hash - viewModel.getCurrentWallet(); - } - - //Transaction failed to be sent - private void txError(TransactionReturn txError) - { - if (dialog != null && dialog.isShowing()) dialog.dismiss(); - dialog = new AWalletAlertDialog(this); - dialog.setIcon(ERROR); - dialog.setTitle(R.string.error_transaction_failed); - dialog.setMessage(txError.throwable.getMessage()); - dialog.setButtonText(R.string.button_ok); - dialog.setButtonListener(v -> { - dialog.dismiss(); - }); - dialog.show(); - confirmationDialog.dismiss(); - } - - private void onWallet(Wallet wallet) - { - this.wallet = wallet; - if (!TextUtils.isEmpty(eventKey)) - { - handleEvent(wallet); - } - else - { - handleTransaction(); - } - - setupFunctions(); - } - - private void setupFunctions() - { - if (token != null) - { - functionBar.revealButtons(); - List functions = new ArrayList<>(); - if (pendingTxUpdate != null) - { - functions.add(R.string.speedup_transaction); - functions.add(R.string.go_to_token); - functions.add(R.string.cancel_transaction); - } - else - { - functions.add(R.string.go_to_token); - } - - functionBar.setupFunctions(this, functions); - } - } - - private void handleTransaction() - { - Transaction transaction = viewModel.fetchTransaction(transactionHash); - if (transaction == null) return; - - TextView eventTime = findViewById(R.id.event_time); - TextView eventAmount = findViewById(R.id.event_amount); - TextView eventAction = findViewById(R.id.event_action); - TextView eventActionSymbol = findViewById(R.id.event_action_symbol); - - //date - eventTime.setText(Utils.localiseUnixTime(getApplicationContext(), transaction.timeStamp)); - //icon - token = getOperationToken(transaction); - String sym = token != null ? token.tokenInfo.symbol : ETH_SYMBOL; - icon.bindData(token, viewModel.getAssetDefinitionService()); - //status - if (token != null) icon.setStatusIcon(token.getTxStatus(transaction)); - - String operationName = token.getOperationName(transaction, this); - - transaction.getDestination(token); - eventAction.setText(operationName); - eventAction.append(" " + sym); - - //amount - String transactionValue = token.getTransactionResultValue(transaction, TRANSACTION_BALANCE_PRECISION); - - if (!token.shouldShowSymbol(transaction) && transaction.input.length() >= FUNCTION_LENGTH) - { - eventAmount.setText(transaction.input.substring(0, FUNCTION_LENGTH)); - eventAction.setText(operationName + " " + getString(R.string.sent_to, token.getFullName())); - } - else if (TextUtils.isEmpty(transactionValue)) - { - eventAmount.setVisibility(View.GONE); - } - else - { - eventAmount.setText(transactionValue); - } - - startPendingUpdate(); - - String supplementalTxt = transaction.getSupplementalInfo(token.getWallet(), viewModel.getTokensService().getNetworkName(token.tokenInfo.chainId)); - if (!TextUtils.isEmpty(supplementalTxt)) - { - eventDetail.setupTransactionView(transaction, token, viewModel.getAssetDefinitionService(), supplementalTxt); - } - else if (token.isERC721()) - { - setupERC721TokenDetail(transaction); - } - else if (token.getInterfaceSpec() == ContractType.ERC1155) - { - setupAmountDisplay(transaction); - } - - setChainName(transaction); - } - - private void setupAmountDisplay(Transaction transaction) - { - if (transferData != null) - { - //Build a list of transfer assets - fetchMetadata = token.buildAssetList(transferData) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(nftList -> displayTransferTokens(nftList, transaction), throwable -> { }); - } - } - - private void displayTransferTokens(List nftAssets, Transaction transaction) - { - if (nftAssets.size() > 0) - { - AmountDisplayWidget amountDisplay = findViewById(R.id.amount_display); - amountDisplay.setVisibility(View.VISIBLE); - amountDisplay.setAmountFromAssetList(nftAssets); - TextView eventAmount = findViewById(R.id.event_amount); - TextView eventAction = findViewById(R.id.event_action); - int assetCount = 0; - for (NFTAsset asset : nftAssets) - { - assetCount += asset.getSelectedBalance().intValue(); - } - - String operationName = transferData.getOperationPrefix() + " " + assetCount + " " + token.getSymbol(); - - eventAmount.setText(operationName); - - if (transaction.transactionInput == null || transaction.transactionInput.type == TransactionType.CONTRACT_CALL) - { - eventAction.setText(transferData.getTitle()); - } - } - } - - private void startPendingUpdate() - { - Transaction transaction = viewModel.fetchTransaction(transactionHash); - if (transaction == null || !transaction.isPending()) return; - - pendingStart = transaction.timeStamp; - - //now set up the transaction pending time - LinearLayout txPending = findViewById(R.id.pending_time_layout); - txPending.setVisibility(View.VISIBLE); - - pendingTxUpdate = Observable.interval(0, 1, TimeUnit.SECONDS) - .doOnNext(l -> { - runOnUiThread(() -> { - long pendingTimeInSeconds = (System.currentTimeMillis() / 1000) - pendingStart; - TextView pendingText = findViewById(R.id.pending_time); - if (pendingText != null) pendingText.setText(getString(R.string.transaction_pending_for, Utils.convertTimePeriodInSeconds(pendingTimeInSeconds, this))); - checkForUpdate(); - }); - }).subscribe(); - } - - private void checkForUpdate() - { - if (viewModel == null || token == null) return; - - try (Realm realm = viewModel.getRealmInstance(new Wallet(viewModel.getTokenService().getCurrentAddress()))) - { - RealmTransaction realmTransaction = realm.where(RealmTransaction.class) - .equalTo("hash", transactionHash) - .findFirst(); - - if (realmTransaction != null && !realmTransaction.isPending()) - { - Transaction tx = TransactionsRealmCache.convert(realmTransaction); - //tx written, update icon - handler.post(() -> { - icon.setStatusIcon(token.getTxStatus(tx)); - }); - stopPendingUpdate(); - } - } - } - - private void stopPendingUpdate() - { - //now set up the transaction pending time - LinearLayout txPending = findViewById(R.id.pending_time_layout); - if (txPending != null) txPending.setVisibility(View.GONE); - - if (pendingTxUpdate != null && !pendingTxUpdate.isDisposed()) pendingTxUpdate.dispose(); - pendingTxUpdate = null; - } - - private Token getOperationToken(Transaction tx) - { - String operationAddress = tx.getOperationTokenAddress(); - Token operationToken = viewModel.getTokensService().getToken(tx.chainId, operationAddress); - if (operationToken == null && transferData != null) - { - operationToken = viewModel.getTokensService().getToken(tx.chainId, transferData.tokenAddress); - } - - if (operationToken == null) - { - operationToken = viewModel.getCurrency(tx.chainId); - } - - return operationToken; - } - - private void handleEvent(Wallet wallet) - { - RealmAuxData item = viewModel.getTransactionsInteract().fetchEvent(wallet.address, eventKey); - if (item != null) - { - transactionHash = item.getTransactionHash(); - Transaction transaction = viewModel.fetchTransaction(transactionHash); - TextView eventTime = findViewById(R.id.event_time); - TextView eventAmount = findViewById(R.id.event_amount); - TextView eventAction = findViewById(R.id.event_action); - TextView eventActionSymbol = findViewById(R.id.event_action_symbol); - //handle info - //date - eventTime.setText(Utils.localiseUnixTime(getApplicationContext(), item.getResultTime())); - //icon - token = viewModel.getToken(item.getChainId(), item.getTokenAddress()); - tokenId = determineTokenId(item); - - if (transaction == null || token == null) - { - return; //shouldn't get here. - } - - String sym = token.tokenInfo.symbol; - icon.bindData(token, viewModel.getAssetDefinitionService()); - //status - icon.setStatusIcon(item.getEventStatusType()); - //amount - String transactionValue = getEventAmount(item, transaction, true); - if (TextUtils.isEmpty(transactionValue)) - { - eventAmount.setVisibility(View.GONE); - } - else - { - eventAmount.setText(getString(R.string.valueSymbol, transactionValue, sym)); - } - //action - eventAction.setText(item.getTitle(getApplicationContext())); - eventActionSymbol.setText(sym); - - //Is the token an NFT and does the event hold tokenId data? - if (token != null) - { - populateActivityInfo(item, getEventAmount(item, transaction, false)); - } - - setChainName(transaction); - } - } - - private BigInteger determineTokenId(RealmAuxData item) - { - if (token != null && token.isNonFungible() && item.getEventResultMap().containsKey("tokenId")) - { - String tokenIdResult = item.getEventResultMap().get("tokenId").value; - return tokenIdResult.startsWith("0x") ? Numeric.toBigInt(tokenIdResult) : new BigInteger(tokenIdResult); - } - else - { - return BigInteger.ZERO; - } - } - - private void populateActivityInfo(RealmAuxData item, String transactionValue) - { - //check for TokenScript - TokenDefinition def = viewModel.getAssetDefinitionService().getAssetDefinition(item.getChainId(), item.getTokenAddress()); - - //corresponding view for this activity? - String cardName = item.getFunctionId(); - - if (def != null) - { - //look up in activities - TSActivityView view = def.getActivityCards().get(cardName); - if (view != null) - { - scriptViewData = view.getView(ASSET_DETAIL_VIEW_NAME); - } - } - - if (scriptViewData == null) - { - renderDefaultView(item, transactionValue); - return; - } - - tokenView.setChainId(token.tokenInfo.chainId); - tokenView.setWalletAddress(new Address(token.getWallet())); - tokenView.setRpcUrl(viewModel.getBrowserRPC(token.tokenInfo.chainId)); - tokenView.setOnReadyCallback(this); - tokenView.setOnSetValuesListener(this); - tokenView.setKeyboardListenerCallback(this); - parsePass = 1; - viewModel.getAssetDefinitionService().clearResultMap(); - args.clear(); - - //corresponding view. Populate the view - getAttrs(item); - } - - private void renderDefaultView(RealmAuxData item, String transactionValue) - { - eventDetail.setupView(item, token, viewModel.getAssetDefinitionService(), transactionValue); - } - - private String getEventAmount(RealmAuxData eventData, Transaction tx, boolean addSign) - { - Map resultMap = eventData.getEventResultMap(); - int decimals = token != null ? token.tokenInfo.decimals : C.ETHER_DECIMALS; - String value = ""; - switch (eventData.getFunctionId()) - { - case "received": - if (addSign) value += "+ "; - case "sent": - if (value.length() == 0 && addSign) value += "- "; - if (resultMap.containsKey("amount")) - { - value += BalanceUtils.getScaledValueFixed(new BigDecimal(resultMap.get("amount").value), - decimals, 4); - return value; - } - break; - case "approvalObtained": - case "ownerApproved": - if (resultMap.containsKey("value")) - { - value = BalanceUtils.getScaledValueFixed(new BigDecimal(resultMap.get("value").value), - decimals, 4); - return value; - } - break; - default: - break; - } - - if (token != null && tokenId.compareTo(BigInteger.ZERO) > 0) - { - value = "1"; - } - else if (token != null && tx != null) - { - value = token.isEthereum() ? token.getTransactionValue(tx, 4) : tx.getOperationResult(token, 4); - } - - return value; - } - - private void getAttrs(RealmAuxData eventData) - { - findViewById(R.id.layout_select_ticket).setVisibility(View.VISIBLE); - try - { - attrs = viewModel.getAssetDefinitionService().getTokenAttrs(token, tokenId, 1); - //add result map values - if (eventData != null) - { - Map resultMap = eventData.getEventResultMap(); - for (String resultKey : resultMap.keySet()) - { - TokenScriptResult.addPair(attrs, resultKey, resultMap.get(resultKey).value); - } - } - } - catch (Exception e) - { - Timber.e(e); - } - - viewModel.getAssetDefinitionService().resolveAttrs(token, new ArrayList<>(Collections.singletonList(tokenId)), null) - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe(this::onAttr, this::onError, () -> displayFunction(attrs.toString())) - .isDisposed(); - } - - private void onError(Throwable throwable) - { - Timber.e(throwable); - displayFunction(attrs.toString()); - } - - private void onAttr(TokenScriptResult.Attribute attribute) - { - //is the attr incomplete? - Timber.d("ATTR/FA: " + attribute.id + " (" + attribute.name + ")" + " : " + attribute.text); - TokenScriptResult.addPair(attrs, attribute.id, attribute.text); - } - - private void displayFunction(String tokenAttrs) - { - try - { - String magicValues = viewModel.getAssetDefinitionService().getMagicValuesForInjection(token.tokenInfo.chainId); - - String injectedView = tokenView.injectWeb3TokenInit(scriptViewData.tokenView, tokenAttrs, tokenId); - injectedView = tokenView.injectJSAtEnd(injectedView, magicValues); - injectedView = tokenView.injectStyleAndWrapper(injectedView, scriptViewData.style); - - String base64 = Base64.encodeToString(injectedView.getBytes(StandardCharsets.UTF_8), Base64.DEFAULT); - tokenView.loadData(base64, "text/html; charset=utf-8", "base64"); - } - catch (Exception e) - { - fillEmpty(); - } - } - - private void fillEmpty() - { - findViewById(R.id.layout_webwrapper).setVisibility(View.VISIBLE); - tokenView.loadData("No Data", "text/html", "utf-8"); - } - - private void setChainName(Transaction transaction) - { - ChainName chainName = findViewById(R.id.chain_name); - if (transaction.chainId != MAINNET_ID) - { - chainName.setVisibility(View.VISIBLE); - chainName.setChainID(transaction.chainId); - } - else - { - chainName.setVisibility(View.GONE); - } - } - - @Override - public void callToJSComplete(String function, String result) - { - - } - - @Override - public void onSaveInstanceState(@NotNull Bundle outState) - { - super.onSaveInstanceState(outState); - outState.putString(C.EXTRA_ACTION_NAME, eventKey); - outState.putString(C.EXTRA_TXHASH, transactionHash); - outState.putBoolean(C.EXTRA_STATE, isFromTokenHistory); - outState.putParcelable(C.EXTRA_TOKEN_ID, transferData); - } - - @Override - public void enterKeyPressed() - { - KeyboardUtils.hideKeyboard(getCurrentFocus()); - } - - @Override - public void onPageLoaded(WebView view) - { - tokenView.callToJS("refresh()"); - } - - @Override - public void onPageRendered(WebView view) - { - findViewById(R.id.layout_webwrapper).setVisibility(View.VISIBLE); - if (parsePass == 1) - { - tokenView.reload(); - } - - parsePass++; - } - - @Override - public void setValues(Map updates) - { - boolean newValues = false; - //called when values update - for (String key : updates.keySet()) - { - String value = updates.get(key); - String old = args.put(key, updates.get(key)); - if (!value.equals(old)) newValues = true; - } - - if (newValues) - { - viewModel.getAssetDefinitionService().addLocalRefs(args); - //rebuild the view - getAttrs(null); - } - } - - /** - * Activity sheet popup - * - */ - private void checkConfirm(ActionSheetMode mode) - { - Transaction transaction = viewModel.fetchTransaction(transactionHash); - if (transaction == null) return; - - BigInteger minGasPrice = viewModel.calculateMinGasPrice(new BigInteger(transaction.gasPrice)); - - Web3Transaction w3tx = new Web3Transaction(transaction, mode, minGasPrice); - - confirmationDialog = new ActionSheetDialog(this, w3tx, token, null, - transaction.to, viewModel.getTokenService(), this); - confirmationDialog.setupResendTransaction(mode); - confirmationDialog.setCanceledOnTouchOutside(false); - confirmationDialog.show(); - } - - @Override - public void handleClick(String action, int actionId) - { - if (actionId == R.string.speedup_transaction) - { - viewModel.startGasPriceUpdate(token.tokenInfo.chainId); - checkConfirm(ActionSheetMode.SPEEDUP_TRANSACTION); - //resend the transaction to speedup - //viewModel.reSendTransaction(transactionHash, this, token, ConfirmationType.RESEND); - } - else if (actionId == R.string.cancel_transaction) - { - viewModel.startGasPriceUpdate(token.tokenInfo.chainId); - checkConfirm(ActionSheetMode.CANCEL_TRANSACTION); - //cancel the transaction - //viewModel.reSendTransaction(transactionHash, this, token, ConfirmationType.CANCEL_TX); - } - else - { - //go to the token - if (isFromTokenHistory) - { - //go back to token - we arrived here from the token view - finish(); - } - else - { - showTokenDetail(this, token); - } - } - } - - public void showTokenDetail(Activity activity, Token token) - { - TokenDetailRouter tokenDetailRouter = new TokenDetailRouter(); - AssetDefinitionService assetDefinitionService = viewModel.getAssetDefinitionService(); - Wallet defaultWallet = viewModel.getWallet(); - boolean hasDefinition = assetDefinitionService.hasDefinition(token); - switch (token.getInterfaceSpec()) - { - case ETHEREUM: - case ERC20: - case CURRENCY: - case DYNAMIC_CONTRACT: - case LEGACY_DYNAMIC_CONTRACT: - case ETHEREUM_INVISIBLE: - case MAYBE_ERC20: - tokenDetailRouter.open(activity, token.getAddress(), token.tokenInfo.symbol, token.tokenInfo.decimals, - !token.isEthereum(), defaultWallet, token, hasDefinition); - break; - case ERC1155: - tokenDetailRouter.open(activity, token, defaultWallet, hasDefinition); - break; - case ERC721: - case ERC721_LEGACY: - case ERC721_TICKET: - case ERC721_UNDETERMINED: - case ERC721_ENUMERABLE: - tokenDetailRouter.open(activity, token, defaultWallet, false); - break; - case ERC875_LEGACY: - case ERC875: - tokenDetailRouter.openLegacyToken(activity, token, defaultWallet); - break; - default: - break; - } - } - - private void setupERC721TokenDetail(Transaction transaction) - { - if (transferData != null) - { - eventDetail.setupTransferData(transaction, token, transferData); - } - else if (transaction.hasInput() && transaction.transactionInput.isSendOrReceive(transaction)) - { - eventDetail.setupERC721TokenView(token, token.getTransferValueRaw(transaction.transactionInput).toString(), true); - } - } - - //// ActionSheet methods - - @Override - public void getAuthorisation(SignAuthenticationCallback callback) - { - viewModel.getAuthentication(this, callback); - } - - @Override - public void sendTransaction(Web3Transaction finalTx) - { - viewModel.requestSignature(finalTx, viewModel.getWallet(), token.tokenInfo.chainId); - } - - @Override - public void completeSendTransaction(Web3Transaction tx, SignatureFromKey signature) - { - viewModel.sendTransaction(viewModel.getWallet(), token.tokenInfo.chainId, tx, signature); - } - - @Override - public void dismissed(String txHash, long callbackId, boolean actionCompleted) - { - //ActionSheet was dismissed - //refresh page with new txf - if (txHash != null) transactionHash = txHash; - viewModel.getCurrentWallet(); - } - - @Override - public void notifyConfirm(String mode) - { - viewModel.actionSheetConfirm(mode); - } - - ActivityResultLauncher getGasSettings = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), - result -> confirmationDialog.setCurrentGasIndex(result)); - - @Override - public ActivityResultLauncher gasSelectLauncher() - { - return getGasSettings; - } - - @Override - public WalletType getWalletType() - { - return viewModel.getWallet().type; - } -} diff --git a/app/src/main/java/com/alphawallet/app/ui/widget/holder/TokenHolder.java b/app/src/main/java/com/alphawallet/app/ui/widget/holder/TokenHolder.java index 04d4584e2..124ecc212 100644 --- a/app/src/main/java/com/alphawallet/app/ui/widget/holder/TokenHolder.java +++ b/app/src/main/java/com/alphawallet/app/ui/widget/holder/TokenHolder.java @@ -194,7 +194,7 @@ public class TokenHolder extends BinderViewHolder implements View balanceEth.setText(attestation.getAttestationName(td)); balanceCoin.setText(attestation.getAttestationDescription(td)); balanceCoin.setVisibility(View.VISIBLE); - if (attestation.isSmartPass()) + if (attestation.knownIssuerKey()) { tokenIcon.setSmartPassIcon(data.getChain()); } diff --git a/app/src/main/java/com/alphawallet/app/viewmodel/HomeViewModel.java b/app/src/main/java/com/alphawallet/app/viewmodel/HomeViewModel.java index 6eb350adf..754fc2844 100644 --- a/app/src/main/java/com/alphawallet/app/viewmodel/HomeViewModel.java +++ b/app/src/main/java/com/alphawallet/app/viewmodel/HomeViewModel.java @@ -706,10 +706,9 @@ public class HomeViewModel extends BaseViewModel ContractInfo info = td.contracts.get(td.holdingToken); if (attn != null && info.contractInterface.equals("Attestation")) { - //calculate using formula: #{scheme.drop0x}#{address.drop0x.lowercased}#{eventId} - String address = Numeric.cleanHexPrefix(Numeric.toHexString(Keys.getAddress(attn.issuerKey))).toLowerCase(); - String preHash = Numeric.cleanHexPrefix(newFileName).toLowerCase() + address + (!TextUtils.isEmpty(attn.terminationId) ? attn.terminationId : ""); - newFileName = Numeric.toHexString(Hash.keccak256(preHash.getBytes(StandardCharsets.UTF_8))); + //recover the prehash from attestation + byte[] preHash = attn.getCollectionIdPreHash(); + newFileName = Numeric.toHexString(Hash.keccak256(preHash)); } else { diff --git a/app/src/main/java/com/alphawallet/app/web3/Web3TokenView.java b/app/src/main/java/com/alphawallet/app/web3/Web3TokenView.java index 107ff6a73..feecbeddd 100644 --- a/app/src/main/java/com/alphawallet/app/web3/Web3TokenView.java +++ b/app/src/main/java/com/alphawallet/app/web3/Web3TokenView.java @@ -432,7 +432,7 @@ public class Web3TokenView extends WebView if (td != null && td.holdingToken != null) { //use webview - renderTokenscriptView(token, range, assetService, iconified); + renderTokenScriptView(token, range, assetService, iconified); } else { @@ -456,7 +456,7 @@ public class Web3TokenView extends WebView loadData(displayData, "text/html", "utf-8"); } - public void renderTokenscriptView(Token token, TicketRange range, AssetDefinitionService assetService, ViewType itemView) + public void renderTokenScriptView(Token token, TicketRange range, AssetDefinitionService assetService, ViewType itemView) { BigInteger tokenId = range.tokenIds.get(0); diff --git a/app/src/main/res/layout/activity_nft_asset_detail.xml b/app/src/main/res/layout/activity_nft_asset_detail.xml index da2be84ef..5223739a8 100644 --- a/app/src/main/res/layout/activity_nft_asset_detail.xml +++ b/app/src/main/res/layout/activity_nft_asset_detail.xml @@ -44,7 +44,27 @@ android:layout_height="match_parent" android:layout_centerInParent="true" android:gravity="center" - custom:webview_height="350"/> + custom:webview_height="350" /> + + + + + + + + diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 57dac8a30..faaef6266 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -218,7 +218,7 @@ Ticket Tickets Fallida - Coste total de %1d %2s: + Coste total de %1$d %2$s: Importar tickets Cancelar Confirmar compra @@ -244,8 +244,8 @@ El MagicLink expira el: Total: %s Define la expiración del MagicLink: - %1s %2s seleccionados - %1s %2s/ticket + %1$s %2$s seleccionados + %1$s %2$s/ticket Se creará un MagicLink para que los compradores puedan adquirir tus tickets. DD/MM/YYYY Antes de que expire el enlace, cualquiera que tenga el MagicLink puede comprar tus tickets con un clic. @@ -952,7 +952,7 @@ Usar tarjeta de hardware Red no admitida (ID de cadena: %s) Se le solicitó que firmara un mensaje para una cadena no admitida. (ID: %s) - Se le solicitó que firmara un mensaje para la cadena %1s. Actualmente estás en la cadena %2s. + Se le solicitó que firmara un mensaje para la cadena %1$s. Actualmente estás en la cadena %2$s. Se le solicitó que firmara un mensaje para %s. Habilite esta cadena para su sesión de WalletConnect. Ver sesión No se puede manejar la solicitud de sesión de una cadena no admitida. (ID: %s) @@ -971,8 +971,8 @@ Proceder Fichas entrantes Muestre notificaciones cuando su billetera activa reciba tokens. - La billetera %1s ha recibido %2s de %3s. Toque aquí para ver los detalles de la transacción. - La cartera %1s ha recibido %2s. Toque aquí para ver los detalles de la transacción. + La billetera %1$s ha recibido %2$s de %3$s. Toque aquí para ver los detalles de la transacción. + La cartera %1$s ha recibido %2$s. Toque aquí para ver los detalles de la transacción. Quiero recibir notificaciones Saltar por ahora Mientras AlphaWallet está en segundo plano, puede enviarle notificaciones de varios eventos, como cuando su billetera recibe tokens. diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index e29f1615e..db6caa48b 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -985,8 +985,8 @@ Procéder Jetons entrants Afficher les notifications lorsque les jetons sont reçus par votre portefeuille actif. - Le portefeuille %1s a reçu %2s de %3s. Appuyez ici pour afficher les détails de la transaction. - Le portefeuille %1s a reçu %2s. Appuyez ici pour afficher les détails de la transaction. + Le portefeuille %1$s a reçu %2$s de %3$s. Appuyez ici pour afficher les détails de la transaction. + Le portefeuille %1$s a reçu %2$s. Appuyez ici pour afficher les détails de la transaction. Je veux recevoir des notifications Ignorer pour l\'instant Pendant qu\'AlphaWallet est en arrière-plan, il peut vous envoyer des notifications pour divers événements, comme lorsque votre portefeuille reçoit des jetons. diff --git a/app/src/main/res/values-zh/strings.xml b/app/src/main/res/values-zh/strings.xml index 91b46c652..386e8bc6b 100644 --- a/app/src/main/res/values-zh/strings.xml +++ b/app/src/main/res/values-zh/strings.xml @@ -951,7 +951,7 @@ 搜索令牌? 硬件钱包 使用硬件卡 - 不支持的网络(链 ID:%s) + 不支持的网络(链 ID:%s) 您被要求为不受支持的链签署消息。(ID: %s) 您被要求为 %1s 链签署消息。 您目前在 %2s 链上。 您被要求为 %s 的消息签名。 请为您的 WalletConnect 会话启用此链。 diff --git a/lib/src/main/java/com/alphawallet/token/entity/AttestationDefinition.java b/lib/src/main/java/com/alphawallet/token/entity/AttestationDefinition.java index fd370d650..56d0777ad 100644 --- a/lib/src/main/java/com/alphawallet/token/entity/AttestationDefinition.java +++ b/lib/src/main/java/com/alphawallet/token/entity/AttestationDefinition.java @@ -2,15 +2,18 @@ package com.alphawallet.token.entity; import static org.w3c.dom.Node.ELEMENT_NODE; -import com.alphawallet.token.entity.ContractInfo; -import com.alphawallet.token.entity.FunctionDefinition; import com.alphawallet.token.tools.Numeric; import org.w3c.dom.Element; import org.w3c.dom.Node; +import org.web3j.crypto.Keys; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.List; +import java.util.Locale; import java.util.Map; /** @@ -27,8 +30,11 @@ public class AttestationDefinition public final String name; public long chainId; public byte[] issuerKey; //also used to generate collectionId - public String terminationId; //used to generate collectionId + //Note these are in List form to preserve order, important in generating the collectionHash + public List collectionKeys; + public List collectionText; public String replacementFieldId; //used to check if new attestation should replace the old + public String schemaUID; public AttestationDefinition(String name) { @@ -37,11 +43,6 @@ public class AttestationDefinition attributes = null; } - public void handleEventId(Element element) - { - terminationId = element.getTextContent(); - } - public void handleReplacementField(Element element) { replacementFieldId = element.getTextContent(); @@ -50,7 +51,7 @@ public class AttestationDefinition public void handleKey(Element element) { //should be the key itself - String key = Numeric.cleanHexPrefix(element.getTextContent()); + String key = Numeric.cleanHexPrefix(element.getTextContent().trim()); if (key.length() == 130 && key.startsWith("04")) { key = key.substring(2); @@ -62,7 +63,7 @@ public class AttestationDefinition public ContractInfo addAttributes(Element element) { //get schemaUID attribute - String schemaUID = element.getAttribute("schemaUID"); + schemaUID = element.getAttribute("schemaUID"); String networkStr = element.getAttribute("network"); //this is the backlink to the attestation ContractInfo info = new ContractInfo("Attestation"); @@ -112,4 +113,36 @@ public class AttestationDefinition } } } + + public void handleCollectionFields(Element element) + { + collectionKeys = new ArrayList<>(); + collectionText = new ArrayList<>(); + for (Node n = element.getFirstChild(); n != null; n = n.getNextSibling()) + { + if (n.getNodeType() != ELEMENT_NODE) continue; + Element attnElement = (Element) n; + + if (attnElement.getLocalName().equals("collectionField")) + { + collectionKeys.add(attnElement.getAttribute("name")); + collectionText.add(attnElement.getTextContent()); + } + } + } + + public byte[] getCollectionIdPreHash() + { + //produce the collectionId + StringBuilder sb = new StringBuilder(); + sb.append(Numeric.cleanHexPrefix(schemaUID).toLowerCase(Locale.ROOT)); + sb.append(Keys.getAddress(Numeric.toHexString(issuerKey)).toLowerCase(Locale.ROOT)); + + for (String collectionItem : collectionText) + { + sb.append(collectionItem); + } + + return sb.toString().getBytes(StandardCharsets.UTF_8); + } } diff --git a/lib/src/main/java/com/alphawallet/token/entity/TSTokenViewHolder.java b/lib/src/main/java/com/alphawallet/token/entity/TSTokenViewHolder.java index a8f6ba5ec..dcac4dfbe 100644 --- a/lib/src/main/java/com/alphawallet/token/entity/TSTokenViewHolder.java +++ b/lib/src/main/java/com/alphawallet/token/entity/TSTokenViewHolder.java @@ -15,14 +15,19 @@ public class TSTokenViewHolder public String getView(String viewName) { TSTokenView v = views.get(viewName); - if (v != null) return v.tokenView; - else return null; + if (v != null) + { + return v.tokenView; + } + else + { + return null; + } } public String getViewStyle(String viewName) { TSTokenView v = views.get(viewName); - if (v != null) return globalStyle + v.style; - else return null; + return (globalStyle != null ? globalStyle : "") + (v != null ? v.style : ""); } } diff --git a/lib/src/main/java/com/alphawallet/token/tools/TokenDefinition.java b/lib/src/main/java/com/alphawallet/token/tools/TokenDefinition.java index 57b40371b..6f227e197 100644 --- a/lib/src/main/java/com/alphawallet/token/tools/TokenDefinition.java +++ b/lib/src/main/java/com/alphawallet/token/tools/TokenDefinition.java @@ -556,6 +556,7 @@ public class TokenDefinition switch (card.getLocalName()) { case "token": + case "token-card": processTokenCardElements(card); break; case "card": @@ -928,8 +929,8 @@ public class TokenDefinition case "key": attn.handleKey(attnElement); break; - case "eventId": - attn.handleEventId(attnElement); + case "collectionFields": + attn.handleCollectionFields(attnElement); break; case "idFields": attn.handleReplacementField(attnElement); @@ -939,7 +940,7 @@ public class TokenDefinition //attn.members.add(parseAttestationStruct(attnElement)); //attestation.add(parseAttestationStruct(attnElement)); break; - case "origins": + case "origins": //TODO: Recode this //attn.origin = parseOrigins(attnElement); //advance to function Element functionElement = getFirstChildElement(attnElement); diff --git a/lib/src/main/java/com/alphawallet/token/tools/XMLDSigVerifier.java b/lib/src/main/java/com/alphawallet/token/tools/XMLDSigVerifier.java index f911a34c7..2c7d3aa1d 100644 --- a/lib/src/main/java/com/alphawallet/token/tools/XMLDSigVerifier.java +++ b/lib/src/main/java/com/alphawallet/token/tools/XMLDSigVerifier.java @@ -24,6 +24,7 @@ import java.security.cert.CertificateNotYetValidException; import java.security.cert.PKIXParameters; import java.security.cert.TrustAnchor; import java.security.cert.X509Certificate; +import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; import java.util.List; @@ -243,12 +244,11 @@ public class XMLDSigVerifier { result.issuerPrincipal = signingCert.getIssuerX500Principal().getName(); result.subjectPrincipal = signingCert.getSubjectX500Principal().getName(); result.keyType = signingCert.getSigAlgName(); - for (Object o : xmlKeyInfo.getContent()) + for (XMLStructure o : xmlKeyInfo.getContent()) { - XMLStructure xmlStructure = (XMLStructure) o; - if (xmlStructure instanceof KeyName) + if (o instanceof KeyName) { - result.keyName = ((KeyName) xmlStructure).getName(); + result.keyName = ((KeyName) o).getName(); } } } @@ -260,34 +260,39 @@ public class XMLDSigVerifier { return result; } - private List getCertificateChainFromXML(List xmlElements) throws KeyStoreException { + private List getCertificateChainFromXML(List xmlElements) throws KeyStoreException, ClassCastException { boolean found = false; - List certs = null; + List certs = new ArrayList<>(); for (int i = 0; i < xmlElements.size(); i++) { - XMLStructure xmlStructure = (XMLStructure) xmlElements.get(i); + XMLStructure xmlStructure = xmlElements.get(i); if (xmlStructure instanceof X509Data) { if(found) throw new KeyStoreException("Duplicate X509Data element"); found = true; - certs = (List) ((X509Data) xmlStructure).getContent(); + for (Object o : ((X509Data) xmlStructure).getContent()) + { + if (o instanceof X509Certificate) + { + certs.add((X509Certificate)o); + } + } } } return certs; } - private PublicKey recoverPublicKeyFromXML(List xmlElements) throws KeyStoreException { + private PublicKey recoverPublicKeyFromXML(List xmlElements) throws KeyStoreException { boolean found = false; PublicKey keyVal = null; for (int i = 0; i < xmlElements.size(); i++) { - XMLStructure xmlStructure = (XMLStructure) xmlElements.get(i); - if (xmlStructure instanceof KeyValue) + XMLStructure xmlStructure = xmlElements.get(i); + if (xmlStructure instanceof KeyValue kv) { //should only be one KeyValue if(found) throw new KeyStoreException("Duplicate Key found"); found = true; - KeyValue kv = (KeyValue) xmlStructure; try { keyVal = kv.getPublicKey(); @@ -301,7 +306,7 @@ public class XMLDSigVerifier { return keyVal; } - private X509Certificate selectSigningKeyFromXML(List xmlElements) throws KeyStoreException, CertificateNotYetValidException { + private X509Certificate selectSigningKeyFromXML(List xmlElements) throws KeyStoreException, CertificateNotYetValidException { PublicKey recovered = recoverPublicKeyFromXML(xmlElements); //Certificates from the XML might be in the wrong order List certList = reorderCertificateChain(getCertificateChainFromXML(xmlElements)); @@ -346,16 +351,14 @@ public class XMLDSigVerifier { { if (keyInfo == null) throw new KeySelectorException("Null KeyInfo object!"); PublicKey signer = null; - List list = keyInfo.getContent(); + List list = keyInfo.getContent(); boolean found = false; - for (Object o : list) + for (XMLStructure xmlStructure : list) { - XMLStructure xmlStructure = (XMLStructure) o; - if (xmlStructure instanceof KeyValue) + if (xmlStructure instanceof KeyValue kv) { if(found) throw new KeySelectorException("Duplicate KeyValue"); found = true; - KeyValue kv = (KeyValue) xmlStructure; try { signer = kv.getPublicKey(); @@ -376,7 +379,6 @@ public class XMLDSigVerifier { { throw new KeySelectorException(e.getMessage()); } - ; if (signingCert != null) { return new SimpleKeySelectorResult(signingCert.getPublicKey()); @@ -390,7 +392,7 @@ public class XMLDSigVerifier { private class SimpleKeySelectorResult implements KeySelectorResult { - private PublicKey pk; + private final PublicKey pk; SimpleKeySelectorResult(PublicKey pk) { this.pk = pk; } diff --git a/util/src/main/java/com/alphawallet/scripttool/Ethereum/TransactionHandler.java b/util/src/main/java/com/alphawallet/scripttool/Ethereum/TransactionHandler.java index d352d9254..52dcbb50a 100644 --- a/util/src/main/java/com/alphawallet/scripttool/Ethereum/TransactionHandler.java +++ b/util/src/main/java/com/alphawallet/scripttool/Ethereum/TransactionHandler.java @@ -9,6 +9,7 @@ import org.web3j.abi.datatypes.Address; import org.web3j.abi.datatypes.DynamicArray; import org.web3j.abi.datatypes.Function; import org.web3j.abi.datatypes.Type; +import org.web3j.abi.datatypes.Uint; import org.web3j.abi.datatypes.Utf8String; import org.web3j.abi.datatypes.generated.Uint256; import org.web3j.protocol.Web3j; @@ -55,11 +56,15 @@ public class TransactionHandler { List result = new ArrayList<>(); org.web3j.abi.datatypes.Function function = balanceOfArray(address); - List indices = callSmartContractFunctionArray(function, contractAddress, address); + List> indices = callSmartContractFunctionArray(function, contractAddress, address); if (indices == null) throw new BadContract(); - for (Uint256 val : indices) + for (Type val : indices) { - result.add(val.getValue()); + if (val instanceof Uint) + { + Uint256 uintValue = (Uint256) val; + result.add(uintValue.getValue()); + } } return result; } @@ -143,7 +148,7 @@ public class TransactionHandler return ethCall.getValue(); } - private List callSmartContractFunctionArray( + private List> callSmartContractFunctionArray( org.web3j.abi.datatypes.Function function, String contractAddress, String address) throws Exception { String encodedFunction = FunctionEncoder.encode(function); @@ -154,9 +159,8 @@ public class TransactionHandler List values = FunctionReturnDecoder.decode(value, function.getOutputParameters()); if (values.isEmpty()) return null; - Type T = values.get(0); - Object o = T.getValue(); - return (List) o; + Type T = values.get(0); + return Collections.singletonList(T); } private static org.web3j.abi.datatypes.Function stringParam(String param) {