From f29a056c5cb04859321f6d474e37863e7d395ec4 Mon Sep 17 00:00:00 2001 From: Lyka Labrada Date: Tue, 7 Nov 2023 09:15:20 +0800 Subject: [PATCH 1/8] feat(ui-ux): allow 100% dusd vault to borrow non-dusd tokens (#4129) --- .../Loans/hooks/ValidateLoanAndCollateral.ts | 5 +---- .../Loans/screens/AddOrRemoveCollateralScreen.tsx | 13 +++++++++---- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/mobile-app/app/screens/AppNavigator/screens/Loans/hooks/ValidateLoanAndCollateral.ts b/mobile-app/app/screens/AppNavigator/screens/Loans/hooks/ValidateLoanAndCollateral.ts index 12813cbcdd..e496dba2af 100644 --- a/mobile-app/app/screens/AppNavigator/screens/Loans/hooks/ValidateLoanAndCollateral.ts +++ b/mobile-app/app/screens/AppNavigator/screens/Loans/hooks/ValidateLoanAndCollateral.ts @@ -66,15 +66,12 @@ export function useValidateLoanAndCollateral( (col) => col.symbol === "DUSD", ); - const isNonDUSDLoanAllowed = - !isTakingDUSDLoan && isDFIGreaterThanHalfOfRequiredCollateral; - const isDUSDLoanAllowed = isTakingDUSDLoan && (isDFIGreaterThanHalfOfRequiredCollateral || (isFeatureAvailable("loop_dusd") && isDUSD100PercentOfCollateral)); return { - isLoanAllowed: isNonDUSDLoanAllowed || isDUSDLoanAllowed, + isLoanAllowed: isDUSDLoanAllowed || !isTakingDUSDLoan, }; } diff --git a/mobile-app/app/screens/AppNavigator/screens/Loans/screens/AddOrRemoveCollateralScreen.tsx b/mobile-app/app/screens/AppNavigator/screens/Loans/screens/AddOrRemoveCollateralScreen.tsx index 153b589b30..c72bd56276 100644 --- a/mobile-app/app/screens/AppNavigator/screens/Loans/screens/AddOrRemoveCollateralScreen.tsx +++ b/mobile-app/app/screens/AppNavigator/screens/Loans/screens/AddOrRemoveCollateralScreen.tsx @@ -389,13 +389,18 @@ export function AddOrRemoveCollateralScreen({ route }: Props): JSX.Element { message: addOrRemoveCollateralErrors.InsufficientBalance, }; } else if ( - isFeatureAvailable("loop_dusd") && hasLoan && - vault.loanAmounts.some((loan) => loan.symbol === "DUSD") && - vault.collateralAmounts.every((col) => col.symbol === "DUSD") + vault.loanAmounts.some((loan) => loan.symbol === "DUSD") ) { + const has100PercentDusdCol = vault.collateralAmounts.every( + (col) => col.symbol === "DUSD", + ); if ( - !["DFI", "DUSD"].includes(selectedCollateralItem.token.symbol) || + (isFeatureAvailable("loop_dusd") && + has100PercentDusdCol && + !["DFI", "DUSD"].includes(selectedCollateralItem.token.symbol)) || + (!has100PercentDusdCol && + selectedCollateralItem.token.symbol === "DUSD") || (selectedCollateralItem.token.symbol === "DFI" && isDFILessThanHalfOfRequiredCollateral) ) { From 28a6a17e7c10e179a1a7afb91b4f1eea96cec7bc Mon Sep 17 00:00:00 2001 From: Harsh R <53080940+fullstackninja864@users.noreply.github.com> Date: Wed, 8 Nov 2023 12:35:28 +0530 Subject: [PATCH 2/8] feature(ui-ux): added get private key feature (#4120) * feature(ui-ux): added get private key feature * updated translations and some chore * fix test case * fix type * fix type --------- Co-authored-by: pierregee --- .../components/BottomSheetAddressDetailV2.tsx | 11 +- .../CreateOrEditAddressLabelForm.tsx | 319 ++++++++++++++---- .../TransactionAuthorization.tsx | 50 +-- .../transferDomain/addresses.spec.ts | 2 + package-lock.json | 18 +- package.json | 4 +- shared/translations/languages/de.json | 8 +- shared/translations/languages/es.json | 8 +- shared/translations/languages/fr.json | 8 +- shared/translations/languages/it.json | 8 +- shared/translations/languages/zh-Hans.json | 8 +- shared/translations/languages/zh-Hant.json | 8 +- 12 files changed, 338 insertions(+), 114 deletions(-) diff --git a/mobile-app/app/screens/AppNavigator/screens/Portfolio/components/BottomSheetAddressDetailV2.tsx b/mobile-app/app/screens/AppNavigator/screens/Portfolio/components/BottomSheetAddressDetailV2.tsx index 3f9e33f474..334577215e 100644 --- a/mobile-app/app/screens/AppNavigator/screens/Portfolio/components/BottomSheetAddressDetailV2.tsx +++ b/mobile-app/app/screens/AppNavigator/screens/Portfolio/components/BottomSheetAddressDetailV2.tsx @@ -287,12 +287,9 @@ export const BottomSheetAddressDetailV2 = ( navigation.navigate({ name: props.navigateToScreen.screenName, params: { - title: "Edit wallet label", + title: "", address: item.dvm, - addressLabel: - labeledAddresses != null - ? labeledAddresses[item.dvm] - : "", + addressLabel: { label: displayAddressLabel }, index: index + 1, type: "edit", onSaveButtonPress: (labelAddress: LabeledAddress) => { @@ -320,9 +317,9 @@ export const BottomSheetAddressDetailV2 = ( testID={`address_edit_indicator_${displayAddress}`} > (false); + const [privateKey, setPrivateKey] = useState(""); const [walletAddress, setWalletAddress] = useState([]); const { fetchWalletAddresses } = useWalletAddress(); const walletAddressFromStore = useSelector( @@ -117,6 +136,55 @@ export const CreateOrEditAddressLabelForm = memo( }, }); }; + const { + data: { type }, + } = useWalletNodeContext(); + const isEncrypted = type === "MNEMONIC_ENCRYPTED"; + const showPrivateKey = useCallback(async () => { + if (!isEncrypted) { + return; + } + + dispatch( + transactionQueue.actions.push({ + sign: async ( + account: WhaleWalletAccount, + ): Promise => { + const key = (await account.privateKey()).toString("hex"); + setPrivateKey(key); + return null; + }, + title: translate( + "components/CreateOrEditAddressLabelForm", + "Verify access to view private key", + ), + }), + ); + }, [dispatch, isEncrypted, account]); + + const TOAST_DURATION = 2000; + const copyToClipboard = useCallback( + debounce(() => { + if (showToast) { + return; + } + setShowToast(true); + setTimeout(() => setShowToast(false), TOAST_DURATION); + }, 500), + [showToast], + ); + + useEffect(() => { + if (showToast) { + toast.show(translate("components/toaster", "Copied"), { + type: "wallet_toast", + placement: "top", + duration: TOAST_DURATION, + }); + } else { + toast.hideAll(); + } + }, [showToast]); const getEVMAddress = (address: string) => { const storedWalletAddress = walletAddressFromStore[ @@ -162,70 +230,189 @@ export const CreateOrEditAddressLabelForm = memo( label={domain === DomainType.DVM ? address : getEVMAddress(address)} /> )} - - {translate("components/CreateOrEditAddressLabelForm", "LABEL")} - - { - setLabelInput(text); - validateLabelInput(text); - setLabelInputLength(text.trim().length); - }} - onClearButtonPress={() => { - setLabelInput(""); - setLabelInputErrorMessage(""); - }} - placeholder={translate( - "components/CreateOrEditAddressLabelForm", - "Enter label", - )} - style={tailwind("h-9 w-6/12 flex-grow")} - hasBottomSheet - valid={labelInputErrorMessage === ""} - inlineText={{ - type: "error", - text: translate( - "components/CreateOrEditAddressLabelForm", - labelInputErrorMessage, - ), - }} - testID="address_book_label_input" - /> - {labelInputErrorMessage === "" && ( - - {translate( - "components/CreateOrEditAddressLabelForm", - "{{length}}/40 characters", - { length: labelInputLength.toString() }, + {privateKey === "" ? ( + <> + + {translate("components/CreateOrEditAddressLabelForm", "LABEL")} + + { + setLabelInput(text); + validateLabelInput(text); + setLabelInputLength(text.trim().length); + }} + onClearButtonPress={() => { + setLabelInput(""); + setLabelInputErrorMessage(""); + }} + placeholder={translate( + "components/CreateOrEditAddressLabelForm", + "Enter label", + )} + style={tailwind("h-9 w-6/12 flex-grow")} + hasBottomSheet + valid={labelInputErrorMessage === ""} + inlineText={{ + type: "error", + text: translate( + "components/CreateOrEditAddressLabelForm", + labelInputErrorMessage, + ), + }} + editable={isEditable} + testID="address_book_label_input" + > + {!isEditable && ( + setIsEditable(true)} + testID="edit_label_button" + > + + + )} + + {labelInputErrorMessage === "" && isEditable && ( + + {translate( + "components/CreateOrEditAddressLabelForm", + "{{length}}/40 characters", + { length: labelInputLength.toString() }, + )} + )} - + + {isEditable ? ( + navigation.goBack()} + onSubmit={handleEditSubmit} + displayCancelBtn + title="save_address_label" + /> + ) : ( + navigation.goBack()} + onSubmit={showPrivateKey} + displayCancelBtn + title="show_private_key" + /> + )} + + + ) : ( + <> + + + {translate( + "components/CreateOrEditAddressLabelForm", + "Your private key provides full access to your account and funds.", + )} + + + {translate( + "components/CreateOrEditAddressLabelForm", + "Make sure you keep this private. Do not share this with anyone or take a screenshot of it.", + )} + + + + {translate( + "components/CreateOrEditAddressLabelForm", + "PRIVATE KEY", + )} + + + {!isEditable && ( + { + copyToClipboard(); + await Clipboard.setStringAsync(privateKey); + }} + testID="edit_label_button" + > + + + )} + + + setPrivateKey("")} + label={translate( + "components/CreateOrEditAddressLabelForm", + "Done", + )} + /> + + )} - - - navigation.goBack()} - onSubmit={handleEditSubmit} - displayCancelBtn - title="save_address_label" - /> - ); }, diff --git a/mobile-app/app/screens/TransactionAuthorization/TransactionAuthorization.tsx b/mobile-app/app/screens/TransactionAuthorization/TransactionAuthorization.tsx index dde0e11d35..fad8a48496 100644 --- a/mobile-app/app/screens/TransactionAuthorization/TransactionAuthorization.tsx +++ b/mobile-app/app/screens/TransactionAuthorization/TransactionAuthorization.tsx @@ -71,13 +71,13 @@ export function TransactionAuthorization(): JSX.Element | null { const logger = useLogger(); const dispatch = useAppDispatch(); const transaction = useSelector((state: RootState) => - first(state.transactionQueue) + first(state.transactionQueue), ); const authentication = useSelector( - (state: RootState) => state.authentication.authentication + (state: RootState) => state.authentication.authentication, ); const [transactionStatus, setTransactionStatus] = useState( - TransactionStatus.INIT + TransactionStatus.INIT, ); const [attemptsRemaining, setAttemptsRemaining] = useState(MAX_PASSCODE_ATTEMPT); @@ -102,7 +102,7 @@ export function TransactionAuthorization(): JSX.Element | null { const [title, setTitle] = useState(); const [message, setMessage] = useState(DEFAULT_MESSAGES.message); const [loadingMessage, setLoadingMessage] = useState( - DEFAULT_MESSAGES.loadingMessage + DEFAULT_MESSAGES.loadingMessage, ); const [successMessage, setSuccessMessage] = useState(); const [additionalMessage, setAdditionalMessage] = useState< @@ -200,7 +200,7 @@ export function TransactionAuthorization(): JSX.Element | null { }; const setupNewWallet = ( - passcodePromptPromise: () => Promise + passcodePromptPromise: () => Promise, ): void => { let provider: WalletHdNodeProvider; if (providerData.type === WalletType.MNEMONIC_UNPROTECTED) { @@ -217,25 +217,27 @@ export function TransactionAuthorization(): JSX.Element | null { const onPinSuccess = async ( transaction: DfTxSigner, - signedTx: CTransactionSegWit + signedTx: CTransactionSegWit, ): Promise => { setTransactionStatus(TransactionStatus.AUTHORIZED); await resetPasscodeCounter(); - dispatch( - ocean.actions.queueTransaction({ - tx: signedTx, - onError: transaction.onError, - onConfirmation: transaction.onConfirmation, - onBroadcast: transaction.onBroadcast, - drawerMessages: transaction.drawerMessages, - submitButtonLabel: transaction.submitButtonLabel, - }) - ); // push signed result for broadcasting + if (signedTx) { + dispatch( + ocean.actions.queueTransaction({ + tx: signedTx, + onError: transaction.onError, + onConfirmation: transaction.onConfirmation, + onBroadcast: transaction.onBroadcast, + drawerMessages: transaction.drawerMessages, + submitButtonLabel: transaction.submitButtonLabel, + }), + ); // push signed result for broadcasting + } }; const signTransactionWithActiveAddress = async ( wallet: JellyfishWallet, - retries: number | undefined + retries: number | undefined, ): Promise => { try { const activeIndex = await WalletAddressIndexPersistence.getActive(); @@ -244,7 +246,7 @@ export function TransactionAuthorization(): JSX.Element | null { wallet.get(activeIndex), onRetry, retries, - logger + logger, ); // case 1: success return await onPinSuccess(transaction, signedTx); @@ -429,33 +431,33 @@ export function TransactionAuthorization(): JSX.Element | null { pin={pin} loadingMessage={translate( "screens/TransactionAuthorization", - loadingMessage + loadingMessage, )} successMessage={ successMessage ?? translate( "screens/UnlockWallet", - DEFAULT_MESSAGES.grantedAccessMessage.title + DEFAULT_MESSAGES.grantedAccessMessage.title, ) } authorizedTransactionMessage={{ title: translate( "screens/TransactionAuthorization", - DEFAULT_MESSAGES.authorizedTransactionMessage.title + DEFAULT_MESSAGES.authorizedTransactionMessage.title, ), description: translate( "screens/TransactionAuthorization", - DEFAULT_MESSAGES.authorizedTransactionMessage.description + DEFAULT_MESSAGES.authorizedTransactionMessage.description, ), }} grantedAccessMessage={{ title: translate( "screens/UnlockWallet", - DEFAULT_MESSAGES.grantedAccessMessage.title + DEFAULT_MESSAGES.grantedAccessMessage.title, ), description: translate( "screens/UnlockWallet", - DEFAULT_MESSAGES.grantedAccessMessage.description + DEFAULT_MESSAGES.grantedAccessMessage.description, ), }} isRetry={isRetry} diff --git a/mobile-app/cypress/e2e/functional/transferDomain/addresses.spec.ts b/mobile-app/cypress/e2e/functional/transferDomain/addresses.spec.ts index c84c0f5342..5e07761034 100644 --- a/mobile-app/cypress/e2e/functional/transferDomain/addresses.spec.ts +++ b/mobile-app/cypress/e2e/functional/transferDomain/addresses.spec.ts @@ -196,8 +196,10 @@ context("Portfolio", () => { // Go to edit address book bottom sheet cy.getByTestID("address_edit_icon_address_row_0").click(); + cy.getByTestID("edit_label_button").click(); cy.getByTestID("address_book_label_input") .click() + .clear() .type("New Wallet Label") .blur() .wait(1000); diff --git a/package-lock.json b/package-lock.json index a6dc748647..bdbd7b42f9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -23,8 +23,8 @@ "@reduxjs/toolkit": "^1.9.7", "@shopify/flash-list": "1.4.3", "@waveshq/standard-defichain-jellyfishsdk": "^2.20.0", - "@waveshq/walletkit-core": "^1.3.8", - "@waveshq/walletkit-ui": "^1.3.8", + "@waveshq/walletkit-core": "^1.3.9", + "@waveshq/walletkit-ui": "^1.3.9", "bignumber.js": "^9.1.2", "buffer": "^6.0.3", "classnames": "^2.3.2", @@ -10380,24 +10380,24 @@ } }, "node_modules/@waveshq/walletkit-core": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/@waveshq/walletkit-core/-/walletkit-core-1.3.8.tgz", - "integrity": "sha512-e1NHf+9OerCCq3kqtsUOd+fn3FyvBlQGDRpjlhyGRxdiuLhc3EuyJWbFzglvmPf97aj726Rgh8NrvIlC+8JssQ==", + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@waveshq/walletkit-core/-/walletkit-core-1.3.9.tgz", + "integrity": "sha512-0mp7Zj8gCgbmrR7AcGMaXEepsCPnmBmTisx5ltHBdF6K80WExR3M//j0XbW1eCROdCL4LcT0r+rG1JAJIj+nRw==", "dependencies": { "bignumber.js": "^9.1.2", "ethers": "^5.7.2" } }, "node_modules/@waveshq/walletkit-ui": { - "version": "1.3.8", - "resolved": "https://registry.npmjs.org/@waveshq/walletkit-ui/-/walletkit-ui-1.3.8.tgz", - "integrity": "sha512-pwkiuvfJZMTvf7ckRwfSPvorgoKppWFsbqNIcSwB1m6/6rXi71IwAy1CL3rrAfX6AKJFUmeOI+8Vtj++6m0BxA==", + "version": "1.3.9", + "resolved": "https://registry.npmjs.org/@waveshq/walletkit-ui/-/walletkit-ui-1.3.9.tgz", + "integrity": "sha512-SYNkpfQ4G5VjdSxaAtYucRmPj4uP5fzwKearYG+q6nuVFWIKOtnEEBLTiqUAr13njdbywGVLTZaAM2inTZMFSQ==", "dependencies": { "@stickyjs/jest": "^1.3.2", "@waveshq/standard-defichain-jellyfishsdk": "^2.14.0", "@waveshq/standard-web": "^2.14.0", "@waveshq/standard-web-linter": "^2.14.0", - "@waveshq/walletkit-core": "1.3.8", + "@waveshq/walletkit-core": "1.3.9", "bignumber.js": "^9.1.2", "dayjs": "^1.11.10", "immer": "^9.0.21", diff --git a/package.json b/package.json index da88940593..f4224c59e4 100644 --- a/package.json +++ b/package.json @@ -34,8 +34,8 @@ "@reduxjs/toolkit": "^1.9.7", "@shopify/flash-list": "1.4.3", "@waveshq/standard-defichain-jellyfishsdk": "^2.20.0", - "@waveshq/walletkit-core": "^1.3.8", - "@waveshq/walletkit-ui": "^1.3.8", + "@waveshq/walletkit-core": "^1.3.9", + "@waveshq/walletkit-ui": "^1.3.9", "bignumber.js": "^9.1.2", "buffer": "^6.0.3", "classnames": "^2.3.2", diff --git a/shared/translations/languages/de.json b/shared/translations/languages/de.json index 079c5efc53..966c014e2f 100644 --- a/shared/translations/languages/de.json +++ b/shared/translations/languages/de.json @@ -175,7 +175,13 @@ "Save changes": "Änderungen sichern", "Address label is too long (max 30 characters)": "Adressbezeichnung zu lang (max. 30 Zeichen)", "Invalid label. Maximum of 40 characters.": "Ungültige Bezeichnung. Maximum von 40 Zeichen", - "{{length}}/40 characters": "{{length}}/40 Zeichen" + "{{length}}/40 characters": "{{length}}/40 Zeichen", + "Done": "Fertig", + "Show private key": "Private Key anzeigen", + "Your private key provides full access to your account and funds.": "Mit deinem Private Key hast du vollen Zugriff auf deinen Account und dein Guthaben.", + "Make sure you keep this private. Do not share this with anyone or take a screenshot of it.": "Achte darauf, dass du ihn geheim hältst. Teile ihn mit niemandem und mache keinen Screenshot davon.", + "PRIVATE KEY": "PRIVATE KEY", + "Verify access to view private key": "Zugang zur Anzeige von Private Key bestätigen" }, "screens/AddOrEditAddressBookScreen": { "Edit address label": "Adressbezeichnung bearbeiten", diff --git a/shared/translations/languages/es.json b/shared/translations/languages/es.json index b9c79bc453..d20483d222 100644 --- a/shared/translations/languages/es.json +++ b/shared/translations/languages/es.json @@ -182,7 +182,13 @@ "Save changes": "Guardar cambios", "Address label is too long (max 30 characters)": "El nombre de la dirección es demasiado largo (máx. 30 caracteres)", "Invalid label. Maximum of 40 characters.": "Etiqueta incorrecta. Máximo de 40 caracteres", - "{{length}}/40 characters": "{{length}}/40 caracteres" + "{{length}}/40 characters": "{{length}}/40 caracteres", + "Done": "Hecho", + "Show private key": "Show private key", + "Your private key provides full access to your account and funds.": "Your private key provides full access to your account and funds.", + "Make sure you keep this private. Do not share this with anyone or take a screenshot of it.": "Make sure you keep this private. Do not share this with anyone or take a screenshot of it.", + "PRIVATE KEY": "PRIVATE KEY", + "Verify access to view private key": "Verify access to view private key" }, "screens/AddOrEditAddressBookScreen": { "Edit address label": "Edita el nombre de la dirección", diff --git a/shared/translations/languages/fr.json b/shared/translations/languages/fr.json index d76d88ceff..cb6145b49e 100644 --- a/shared/translations/languages/fr.json +++ b/shared/translations/languages/fr.json @@ -178,7 +178,13 @@ "CONTINUE": "SUIVANT", "Address label is too long (max 30 characters)": "Le nom d'adresse est trop long (30 caractères max)", "Invalid label. Maximum of 40 characters.": "Nom non valide. Maximum de 40 caractères.", - "{{length}}/40 characters": "{{length}}/40 caractères" + "{{length}}/40 characters": "{{length}}/40 caractères", + "Done": "Fait", + "Show private key": "Afficher la clé privée", + "Your private key provides full access to your account and funds.": "Votre clé privée vous donne un accès complet à votre compte et à vos fonds.", + "Make sure you keep this private. Do not share this with anyone or take a screenshot of it.": "Veillez à ce qu'elle reste confidentielle. Ne la communiquez à personne et n'en faites pas de capture d'écran.", + "PRIVATE KEY": "CLÉ PRIVÉE", + "Verify access to view private key": "Vérifier l'accès à l'affichage de la clé privée" }, "screens/AddOrEditAddressBookScreen": { "Edit address label": "Modifier le nom d'adresse", diff --git a/shared/translations/languages/it.json b/shared/translations/languages/it.json index 0e2d132829..66cdedb027 100644 --- a/shared/translations/languages/it.json +++ b/shared/translations/languages/it.json @@ -184,7 +184,13 @@ "Save changes": "Salva modifiche", "Address label is too long (max 30 characters)": "L'etichetta dell'indirizzo è troppo lunga (max 30 caratteri)", "Invalid label. Maximum of 40 characters.": "Invalid label. Maximum of 40 characters.", - "{{length}}/40 characters": "{{length}}/40 characters" + "{{length}}/40 characters": "{{length}}/40 characters", + "Done": "Done", + "Show private key": "Show private key", + "Your private key provides full access to your account and funds.": "Your private key provides full access to your account and funds.", + "Make sure you keep this private. Do not share this with anyone or take a screenshot of it.": "Make sure you keep this private. Do not share this with anyone or take a screenshot of it.", + "PRIVATE KEY": "PRIVATE KEY", + "Verify access to view private key": "Verify access to view private key" }, "screens/AddOrEditAddressBookScreen": { "Edit address label": "Modifica l'etichetta dell'indirizzo", diff --git a/shared/translations/languages/zh-Hans.json b/shared/translations/languages/zh-Hans.json index 17070a24a6..45e6c22e7f 100644 --- a/shared/translations/languages/zh-Hans.json +++ b/shared/translations/languages/zh-Hans.json @@ -175,7 +175,13 @@ "Save changes": "储存修改", "Address label is too long (max 30 characters)": "地址标签太长(最多 30 个字符)", "Invalid label. Maximum of 40 characters.": "Invalid label. Maximum of 40 characters.", - "{{length}}/40 characters": "{{length}}/40 characters" + "{{length}}/40 characters": "{{length}}/40 characters", + "Done": "Done", + "Show private key": "显示私钥", + "Your private key provides full access to your account and funds.": "此私钥有对您的帐户以及资金具有完全访问的权限。", + "Make sure you keep this private. Do not share this with anyone or take a screenshot of it.": "请确保将此私钥保密。 不要与任何人分享或截取此屏幕截图。", + "PRIVATE KEY": "私钥", + "Verify access to view private key": "验证访问权限以查看私钥" }, "screens/AddOrEditAddressBookScreen": { "Edit address label": "更改地址标签", diff --git a/shared/translations/languages/zh-Hant.json b/shared/translations/languages/zh-Hant.json index 947afd74d6..604d399126 100644 --- a/shared/translations/languages/zh-Hant.json +++ b/shared/translations/languages/zh-Hant.json @@ -175,7 +175,13 @@ "Save changes": "儲存修改", "Address label is too long (max 30 characters)": "地址標籤太長(最多 30 個字符)", "Invalid label. Maximum of 40 characters.": "Invalid label. Maximum of 40 characters.", - "{{length}}/40 characters": "{{length}}/40 characters" + "{{length}}/40 characters": "{{length}}/40 characters", + "Done": "Done", + "Show private key": "顯示私鑰", + "Your private key provides full access to your account and funds.": "此私鑰有對您的帳戶以及資金具有完全存取的權限。", + "Make sure you keep this private. Do not share this with anyone or take a screenshot of it.": "請確保將此私鑰保密。 不要與任何人分享或截取此螢幕截圖。", + "PRIVATE KEY": "私鑰", + "Verify access to view private key": "驗證存取權以查看私密金鑰" }, "screens/AddOrEditAddressBookScreen": { "Edit address label": "更改地址標籤", From de4348d00cd71bee129969fd6141bd81b7019d85 Mon Sep 17 00:00:00 2001 From: Lyka Labrada Date: Wed, 8 Nov 2023 15:42:47 +0800 Subject: [PATCH 3/8] feat(ui-ux): display evm tx id and url that points to metascan (#4117) * feat(ui-ux): display evm tx id and url that points to metascan * update code for mapping evm tx --------- Co-authored-by: Pierre Gee --- .../OceanInterface/OceanInterface.tsx | 82 +++++++++++++++++-- .../OceanInterface/TransactionDetail.tsx | 39 +++++++-- .../OceanInterface/TransactionIDButton.tsx | 26 ++++-- shared/contexts/DeFiScanContext.tsx | 8 ++ 4 files changed, 132 insertions(+), 23 deletions(-) diff --git a/mobile-app/app/components/OceanInterface/OceanInterface.tsx b/mobile-app/app/components/OceanInterface/OceanInterface.tsx index 4c9926e154..ad2528e8d4 100644 --- a/mobile-app/app/components/OceanInterface/OceanInterface.tsx +++ b/mobile-app/app/components/OceanInterface/OceanInterface.tsx @@ -1,6 +1,12 @@ -import { useDeFiScanContext } from "@shared-contexts/DeFiScanContext"; +import { + getMetaScanTxUrl, + useDeFiScanContext, +} from "@shared-contexts/DeFiScanContext"; import { useWalletContext } from "@shared-contexts/WalletContext"; -import { useWhaleApiClient } from "@waveshq/walletkit-ui/dist/contexts"; +import { + useNetworkContext, + useWhaleApiClient, +} from "@waveshq/walletkit-ui/dist/contexts"; import { CTransactionSegWit } from "@defichain/jellyfish-transaction/dist"; import { WhaleApiClient } from "@defichain/whale-api-client"; import { Transaction } from "@defichain/whale-api-client/dist/api/transactions"; @@ -30,11 +36,27 @@ const MAX_AUTO_RETRY = 1; const MAX_TIMEOUT = 300000; const INTERVAL_TIME = 5000; +enum VmmapTypes { + Auto = 0, + BlockNumberDVMToEVM = 1, + BlockNumberEVMToDVM = 2, + BlockHashDVMToEVM = 3, + BlockHashEVMToDVM = 4, + TxHashDVMToEVM = 5, + TxHasEVMToDVM = 6, +} + +interface VmmapResult { + input: string; + type: string; + output: string; +} + async function broadcastTransaction( tx: CTransactionSegWit, client: WhaleApiClient, retries: number = 0, - logger: NativeLoggingProps + logger: NativeLoggingProps, ): Promise { try { return await client.rawtx.send({ hex: tx.toHex() }); @@ -50,7 +72,7 @@ async function broadcastTransaction( async function waitForTxConfirmation( id: string, client: WhaleApiClient, - logger: NativeLoggingProps + logger: NativeLoggingProps, ): Promise { const initialTime = getEnvironment(getReleaseChannel()).debug ? 5000 : 30000; let start = initialTime; @@ -96,17 +118,21 @@ export function OceanInterface(): JSX.Element | null { const client = useWhaleApiClient(); const { wallet, address } = useWalletContext(); const { getTransactionUrl } = useDeFiScanContext(); + const { network } = useNetworkContext(); // store const { height, err: e } = useSelector((state: RootState) => state.ocean); const transaction = useSelector((state: RootState) => - firstTransactionSelector(state.ocean) + firstTransactionSelector(state.ocean), ); const slideAnim = useRef(new Animated.Value(0)).current; // state const [tx, setTx] = useState(transaction); const [err, setError] = useState(e?.message); const [txUrl, setTxUrl] = useState(); + // evm tx state + const [evmTxId, setEvmTxId] = useState(); + const [evmTxUrl, setEvmTxUrl] = useState(); const dismissDrawer = useCallback(() => { setTx(undefined); @@ -114,6 +140,46 @@ export function OceanInterface(): JSX.Element | null { slideAnim.setValue(0); }, [slideAnim]); + const getEvmTxId = async (oceanTxId: string) => { + const vmmap: VmmapResult = await client.rpc.call( + "vmmap", + [oceanTxId, VmmapTypes.TxHashDVMToEVM], + "lossless", + ); + return vmmap.output; + }; + + useEffect(() => { + // get evm tx id and url (if any) + const fetchEvmTx = async (txId: string) => { + try { + const mappedEvmTxId = await getEvmTxId(txId); + const txUrl = getMetaScanTxUrl(network, mappedEvmTxId); + setEvmTxId(mappedEvmTxId); + setEvmTxUrl(txUrl); + } catch (error) { + logger.error(error); + } + }; + + if (tx !== undefined) { + const isTransferDomainTx = tx?.tx.vout.some( + (vout) => + vout.script?.stack.some( + (item: any) => + item.type === "OP_DEFI_TX" && + item.tx?.name === "OP_DEFI_TX_TRANSFER_DOMAIN", + ), + ); + if (isTransferDomainTx) { + fetchEvmTx(tx.tx.txId); + } + } else { + setEvmTxId(undefined); + setEvmTxUrl(undefined); + } + }, [tx]); + useEffect(() => { // last available job will remained in this UI state until get dismissed if (transaction !== undefined) { @@ -133,7 +199,7 @@ export function OceanInterface(): JSX.Element | null { .then(async () => { try { setTxUrl( - getTransactionUrl(transaction.tx.txId, transaction.tx.toHex()) + getTransactionUrl(transaction.tx.txId, transaction.tx.toHex()), ); } catch (e) { logger.error(e); @@ -159,7 +225,7 @@ export function OceanInterface(): JSX.Element | null { logger.error(e); title = translate( "screens/OceanInterface", - "Sent (Pending confirmation)" + "Sent (Pending confirmation)", ); oceanStatusCode = TransactionStatusCode.pending; } @@ -224,6 +290,8 @@ export function OceanInterface(): JSX.Element | null { oceanStatusCode={tx.oceanStatusCode} txUrl={txUrl} txid={tx.tx.txId} + evmTxId={evmTxId} + evmTxUrl={evmTxUrl} /> ) )} diff --git a/mobile-app/app/components/OceanInterface/TransactionDetail.tsx b/mobile-app/app/components/OceanInterface/TransactionDetail.tsx index d151105029..b31a495252 100644 --- a/mobile-app/app/components/OceanInterface/TransactionDetail.tsx +++ b/mobile-app/app/components/OceanInterface/TransactionDetail.tsx @@ -16,6 +16,8 @@ interface TransactionDetailProps { broadcasted: boolean; txid?: string; txUrl?: string; + evmTxId?: string; + evmTxUrl?: string; onClose: () => void; title?: string; oceanStatusCode?: TransactionStatusCode; @@ -25,10 +27,13 @@ export function TransactionDetail({ broadcasted, txid, txUrl, + evmTxId, + evmTxUrl, onClose, title, oceanStatusCode, }: TransactionDetailProps): JSX.Element { + const hasEvmTx = evmTxId !== undefined && evmTxUrl !== undefined; title = title ?? translate("screens/OceanInterface", "Broadcasting..."); return ( @@ -42,7 +47,7 @@ export function TransactionDetail({ { "border-darkwarning-500": oceanStatusCode === TransactionStatusCode.pending, - } + }, )} light={tailwind( "bg-mono-light-v2-00 border-mono-light-v2-500", @@ -53,14 +58,14 @@ export function TransactionDetail({ { "border-warning-500": oceanStatusCode === TransactionStatusCode.pending, - } + }, )} style={tailwind( "w-full rounded-lg-v2 flex flex-row items-center border-0.5", { "pl-5": broadcasted, "px-5": !broadcasted, - } + }, )} > {!broadcasted ? ( @@ -94,12 +99,28 @@ export function TransactionDetail({ {title} - {txid !== undefined && txUrl !== undefined && ( - await gotoExplorer(txUrl)} - txid={txid} - /> - )} + + {txid !== undefined && txUrl !== undefined && ( + await gotoExplorer(txUrl)} + txid={txid} + {...(hasEvmTx && { + styleProps: "w-1/2 pr-2", + dark: tailwind("border-r border-mono-dark-v2-300"), + light: tailwind("border-r border-mono-light-v2-300"), + })} + /> + )} + {hasEvmTx && ( + await gotoExplorer(evmTxUrl)} + txid={evmTxId} + styleProps="w-1/2 pl-2" + /> + )} + {broadcasted && } diff --git a/mobile-app/app/components/OceanInterface/TransactionIDButton.tsx b/mobile-app/app/components/OceanInterface/TransactionIDButton.tsx index f0861eb4f8..1bfc6d52c6 100644 --- a/mobile-app/app/components/OceanInterface/TransactionIDButton.tsx +++ b/mobile-app/app/components/OceanInterface/TransactionIDButton.tsx @@ -1,21 +1,33 @@ -import { TouchableOpacity } from "react-native"; import { tailwind } from "@tailwind"; -import { ThemedIcon, ThemedTextV2 } from "@components/themed"; +import { + ThemedIcon, + ThemedProps, + ThemedTextV2, + ThemedTouchableOpacity, +} from "@components/themed"; interface TransactionIDButtonProps { + testID: string; txid: string; onPress?: () => void; + styleProps?: string; } export function TransactionIDButton({ + testID, txid, onPress, -}: TransactionIDButtonProps): JSX.Element { + styleProps = "w-8/12", + light = tailwind("border-0"), + dark = tailwind("border-0"), +}: TransactionIDButtonProps & ThemedProps): JSX.Element { return ( - - + ); } diff --git a/shared/contexts/DeFiScanContext.tsx b/shared/contexts/DeFiScanContext.tsx index 48c7cd813b..1f7a74cd1a 100644 --- a/shared/contexts/DeFiScanContext.tsx +++ b/shared/contexts/DeFiScanContext.tsx @@ -127,3 +127,11 @@ export function getMetaScanTokenUrl( const tokenAddress = getAddressFromDST20TokenId(tokenId); return `${baseMetaScanUrl}/token/${tokenAddress}${networkParams}`; } + +export function getMetaScanTxUrl( + network: EnvironmentNetwork, + id: string, +): string { + const networkParams = getNetworkParams(network); + return `${baseMetaScanUrl}/tx/${id}${networkParams}`; +} From c074dcfc7ba0aca6444fb543c2956759b0df4471 Mon Sep 17 00:00:00 2001 From: Lyka Labrada Date: Thu, 9 Nov 2023 09:57:24 +0800 Subject: [PATCH 4/8] fix(ui-ux): add linear gradient to from address on evm (#4113) * fix(ui-ux): add linear gradient to from address on evm * move from address and label into a separate component --- mobile-app/app/components/SummaryTitle.tsx | 82 ++++++++++++++-------- 1 file changed, 52 insertions(+), 30 deletions(-) diff --git a/mobile-app/app/components/SummaryTitle.tsx b/mobile-app/app/components/SummaryTitle.tsx index 280cc2b62d..05af981acd 100644 --- a/mobile-app/app/components/SummaryTitle.tsx +++ b/mobile-app/app/components/SummaryTitle.tsx @@ -99,29 +99,33 @@ export function SummaryTitle(props: ISummaryTitleProps): JSX.Element { > {translate("screens/common", "From")} - - - - {props.fromAddressLabel ?? props.fromAddress} - - + + + ) : ( + // DVM from address + + + + )} )} @@ -162,10 +166,8 @@ export function SummaryTitle(props: ISummaryTitleProps): JSX.Element { tailwind( "text-mono-light-v2-00 text-sm font-normal-v2 tracking-[0.24]", ), - { - minWidth: 10, - maxWidth: 108, - }, + // eslint-disable-next-line react-native/no-inline-styles + { minWidth: 10, maxWidth: 108 }, ]} light={tailwind("text-mono-light-v2-1000")} dark={tailwind("text-mono-dark-v2-1000")} @@ -198,10 +200,7 @@ export function SummaryTitle(props: ISummaryTitleProps): JSX.Element { style={[ tailwind("text-sm font-normal-v2"), // eslint-disable-next-line react-native/no-inline-styles - { - minWidth: 10, - maxWidth: 108, - }, + { minWidth: 10, maxWidth: 108 }, ]} light={tailwind("text-mono-light-v2-900")} dark={tailwind("text-mono-dark-v2-900")} @@ -217,3 +216,26 @@ export function SummaryTitle(props: ISummaryTitleProps): JSX.Element { ); } + +function FromAddress(props: { + fromAddress: string; + fromAddressLabel?: string | null; +}): JSX.Element { + return ( + <> + + + {props.fromAddressLabel ?? props.fromAddress} + + + ); +} From 13cf06371fef3fc672964c96b3b0b8a639d7890c Mon Sep 17 00:00:00 2001 From: Lyka Labrada Date: Thu, 9 Nov 2023 09:57:45 +0800 Subject: [PATCH 5/8] fix(ui-ux): allow adding of dusd collateral with active dusd loan (#4131) --- .../screens/AddOrRemoveCollateralScreen.tsx | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/mobile-app/app/screens/AppNavigator/screens/Loans/screens/AddOrRemoveCollateralScreen.tsx b/mobile-app/app/screens/AppNavigator/screens/Loans/screens/AddOrRemoveCollateralScreen.tsx index c72bd56276..8dd2f6185f 100644 --- a/mobile-app/app/screens/AppNavigator/screens/Loans/screens/AddOrRemoveCollateralScreen.tsx +++ b/mobile-app/app/screens/AppNavigator/screens/Loans/screens/AddOrRemoveCollateralScreen.tsx @@ -390,25 +390,14 @@ export function AddOrRemoveCollateralScreen({ route }: Props): JSX.Element { }; } else if ( hasLoan && - vault.loanAmounts.some((loan) => loan.symbol === "DUSD") + vault.loanAmounts.some((loan) => loan.symbol === "DUSD") && + vault.collateralAmounts.every((col) => col.symbol === "DUSD") && + !["DFI", "DUSD"].includes(selectedCollateralItem.token.symbol) ) { - const has100PercentDusdCol = vault.collateralAmounts.every( - (col) => col.symbol === "DUSD", - ); - if ( - (isFeatureAvailable("loop_dusd") && - has100PercentDusdCol && - !["DFI", "DUSD"].includes(selectedCollateralItem.token.symbol)) || - (!has100PercentDusdCol && - selectedCollateralItem.token.symbol === "DUSD") || - (selectedCollateralItem.token.symbol === "DFI" && - isDFILessThanHalfOfRequiredCollateral) - ) { - return { - testID: "full_dusd_col_affected_vault_error", - message: addOrRemoveCollateralErrors.DusdAffectVault, - }; - } + return { + testID: "full_dusd_col_affected_vault_error", + message: addOrRemoveCollateralErrors.DusdAffectVault, + }; } }; const getRemoveCollateralErrorMessage = (): From b4961f94f603894d5ed6e43484f1a0ba62b17452 Mon Sep 17 00:00:00 2001 From: Harsh R <53080940+fullstackninja864@users.noreply.github.com> Date: Thu, 9 Nov 2023 07:32:01 +0530 Subject: [PATCH 6/8] fix(ui-ux): fixed convert screen issue for evm domain (#4122) --- .../Portfolio/screens/ConvertScreen.tsx | 44 ++++++++++--------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/mobile-app/app/screens/AppNavigator/screens/Portfolio/screens/ConvertScreen.tsx b/mobile-app/app/screens/AppNavigator/screens/Portfolio/screens/ConvertScreen.tsx index 0db762d45e..c1b1c8bd4e 100644 --- a/mobile-app/app/screens/AppNavigator/screens/Portfolio/screens/ConvertScreen.tsx +++ b/mobile-app/app/screens/AppNavigator/screens/Portfolio/screens/ConvertScreen.tsx @@ -59,6 +59,7 @@ export function ConvertScreen(props: Props): JSX.Element { const { getTokenPrice } = useTokenPrice(); const { isLight } = useThemeContext(); const { domain, isEvmFeatureEnabled } = useDomainContext(); + const isEvmDomain = domain === DomainType.EVM; const client = useWhaleApiClient(); const logger = useLogger(); const tokens = useSelector((state: RootState) => @@ -185,10 +186,10 @@ export function ConvertScreen(props: Props): JSX.Element { function onPercentagePress(amount: string, type: AmountButtonTypes): void { setAmount(amount); - showToast(type, domain); + showToast(type); } - function showToast(type: AmountButtonTypes, domain: DomainType): void { + function showToast(type: AmountButtonTypes): void { if (sourceToken === undefined) { return; } @@ -201,9 +202,7 @@ export function ConvertScreen(props: Props): JSX.Element { const toastOption = { unit: translate( "screens/ConvertScreen", - `${sourceToken.token.displayTextSymbol}${ - domain === DomainType.EVM ? " (EVM)" : "" - }`, + `${sourceToken.token.displayTextSymbol}${isEvmDomain ? " (EVM)" : ""}`, ), percent: type, }; @@ -251,7 +250,7 @@ export function ConvertScreen(props: Props): JSX.Element { } else { return isEvmFeatureEnabled ? [defaultEvmTargetToken] : []; } - } else if (domain === DomainType.EVM && sourceToken.tokenId === "0_evm") { + } else if (isEvmDomain && sourceToken.tokenId === "0_evm") { return isEvmFeatureEnabled ? [defaultEvmTargetToken] : []; } } @@ -310,7 +309,7 @@ export function ConvertScreen(props: Props): JSX.Element { } else if (domain === DomainType.DVM && item.tokenId === "0") { // If DFI Token -> no default updatedTargetToken = undefined; - } else if (domain === DomainType.EVM) { + } else if (isEvmDomain) { // If EVM -> choose DVM equivalent updatedTargetToken = dvmTokens.find( @@ -566,21 +565,24 @@ export function ConvertScreen(props: Props): JSX.Element { /> - {sourceToken.tokenId === "0" && isEvmFeatureEnabled && ( - { - navigateToTokenSelectionScreen(TokenListType.To); - }} - status={TokenDropdownButtonStatus.Enabled} - /> - )} + {sourceToken.tokenId === "0" && + isEvmFeatureEnabled && + !isEvmDomain && ( + { + navigateToTokenSelectionScreen(TokenListType.To); + }} + status={TokenDropdownButtonStatus.Enabled} + /> + )} {((sourceToken.tokenId !== "0" && targetToken) || - (!isEvmFeatureEnabled && targetToken)) && ( + (!isEvmFeatureEnabled && targetToken) || + (isEvmFeatureEnabled && isEvmDomain && targetToken)) && ( Date: Thu, 16 Nov 2023 11:24:29 +0530 Subject: [PATCH 7/8] chore(core): updated docker file (#4133) --- docker-compose.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docker-compose.yml b/docker-compose.yml index ebeb48eb8d..472dd5da07 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -17,7 +17,7 @@ services: - "/var/run/docker.sock:/var/run/docker.sock:ro" defi-blockchain: - image: defi/defichain:master-11717b5810 + image: defi/defichain:4.0.0 ports: - "19554:19554" - "19551:19551" @@ -64,7 +64,7 @@ services: - RUST_LOG=debug defi-playground: - image: ghcr.io/birthdayresearch/playground-api:4.0.0-rc.1.2 + image: ghcr.io/birthdayresearch/playground-api:4.0.1 depends_on: - defi-blockchain ports: @@ -77,7 +77,7 @@ services: - "traefik.http.routers.playground.entrypoints=web" defi-whale: - image: ghcr.io/birthdayresearch/whale-api:4.0.0-rc.1.2 + image: ghcr.io/birthdayresearch/whale-api:4.0.1 depends_on: - defi-blockchain ports: From 5b6389798f99cc2a537445a1963295b36ecdcdef Mon Sep 17 00:00:00 2001 From: Harsh R <53080940+fullstackninja864@users.noreply.github.com> Date: Thu, 16 Nov 2023 11:34:24 +0530 Subject: [PATCH 8/8] chore(core): added ethrpc url for main net (#4134) * chore(core): added ethrpc url for mainnet * Update mobile-app/app/contexts/CustomServiceProvider.tsx --- mobile-app/app/contexts/CustomServiceProvider.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mobile-app/app/contexts/CustomServiceProvider.tsx b/mobile-app/app/contexts/CustomServiceProvider.tsx index 7be70b2dd9..f121adbfa0 100644 --- a/mobile-app/app/contexts/CustomServiceProvider.tsx +++ b/mobile-app/app/contexts/CustomServiceProvider.tsx @@ -112,7 +112,7 @@ function getEthRpcUrl(network: EnvironmentNetwork) { case EnvironmentNetwork.Changi: return "http://34.34.156.49:20551"; // TODO: add final eth rpc url for changi, devnet and remote playground case EnvironmentNetwork.MainNet: - return "https://changi.dfi.team"; // TODO: add final eth rpc url for mainnet, with proper domain name + return "https://eth.mainnet.ocean.jellyfishsdk.com"; // TODO: add final eth rpc url for mainnet, with proper domain name case EnvironmentNetwork.TestNet: default: return "https://eth.testnet.ocean.jellyfishsdk.com";