diff --git a/package.json b/package.json index 8c35df26dd..3d3c930fbc 100644 --- a/package.json +++ b/package.json @@ -195,6 +195,7 @@ "@cardano-foundation/ledgerjs-hw-app-cardano": "5.0.0", "@iohk-jormungandr/wallet-js": "0.5.0-pre7", "@ledgerhq/hw-transport-node-hid": "5.51.1", + "@unstoppabledomains/resolution": "^7.1.4", "aes-js": "3.1.2", "bech32": "2.0.0", "bignumber.js": "9.0.1", diff --git a/source/common/types/address-introspection.types.ts b/source/common/types/address-introspection.types.ts index fca6abf3cf..6de8f28d41 100644 --- a/source/common/types/address-introspection.types.ts +++ b/source/common/types/address-introspection.types.ts @@ -1,7 +1,9 @@ +import { Domain } from 'domain'; + export type IntrospectAddressRequest = { input: string; }; -export type AddressStyle = 'Byron' | 'Icarus' | 'Shelley'; +export type AddressStyle = 'Byron' | 'Icarus' | 'Shelley' | DomainAddress; export type AddressType = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 14 | 15; export type ChainPointer = { slot_num: number; @@ -18,6 +20,7 @@ export type ByronAddress = AddressBase & { address_root: string; derivation_path: string; }; +export type DomainAddress = 'ENS' | 'UNS'; export type IcarusAddress = AddressBase & { address_root: string; }; diff --git a/source/common/utils/unsResolution.ts b/source/common/utils/unsResolution.ts new file mode 100644 index 0000000000..f354159c01 --- /dev/null +++ b/source/common/utils/unsResolution.ts @@ -0,0 +1,59 @@ +import { Resolution, ResolutionError } from '@unstoppabledomains/resolution'; +import { defineMessages } from 'react-intl'; +// import globalMessages from '../../renderer/app/i18n/global-messages'; + +const messages = defineMessages({ + unsupported: { + id: 'wallet.transaction.send.domain.unsupported', + defaultMessage: '!!!Unsupported Domain', + description: 'Unsupported Domain Error', + }, + notFound: { + id: 'wallet.transaction.send.domain.recordNotFound', + defaultMessage: '!!!No Cardano record found for this domain', + description: 'Not Found domain error', + }, + unregistered: { + id: 'wallet.transaction.send.domain.unregistered', + defaultMessage: '!!!Domain not registered', + description: 'Domain not registered error', + }, + networkLabel: { + id: 'wallet.receive.pdf.networkLabel', + defaultMessage: '!!!Cardano Network:', + description: 'PDF networkLabel', + }, +}); + +// enum ErrorMessage { +// NOT_FOUND = 'domainNotFound', +// UNREGISTED = 'domainUnregistered', +// UNSUPPORTED = 'domainUnsupported', +// } + +export const resolveUNSAddress = async (unsAddress: string, intl: any) => { + try { + return await new Resolution().addr(unsAddress, 'ADA'); + } catch (error) { + switch (error.code) { + case 'UnsupportedDomain': + throw new Error( + intl.formatMessage(messages.wallet.transaction.send.domain.invalid) + ); + case 'RecordNotFound': + throw new Error( + intl.formatMessage( + messages.wallet.transaction.send.domain.recordNotFound + ) + ); + case 'UnregisteredDomain': + throw new Error( + intl.formatMessage( + messages.wallet.transaction.send.domain.unregistered + ) + ); + default: + throw new Error('Error resolving domain'); + } + } +}; diff --git a/source/main/webpack.config.js b/source/main/webpack.config.js index 7cf1be8769..063695f7b6 100644 --- a/source/main/webpack.config.js +++ b/source/main/webpack.config.js @@ -85,6 +85,8 @@ module.exports = { new webpack.DefinePlugin( Object.assign( { + devContentSecurityPolicy: + "connect-src 'self' ws://localhost:3000/cpp 'unsafe-eval'", 'process.env.API_VERSION': JSON.stringify( process.env.API_VERSION || 'dev' ), diff --git a/source/main/windows/main.ts b/source/main/windows/main.ts index 1e7fe70d88..500840470a 100644 --- a/source/main/windows/main.ts +++ b/source/main/windows/main.ts @@ -31,6 +31,7 @@ type WindowOptionsType = { width: number; height: number; webPreferences: { + webSecurity: boolean; nodeIntegration: boolean; webviewTag: boolean; enableRemoteModule: boolean; @@ -45,6 +46,7 @@ export const createMainWindow = (locale: string, windowBounds?: Rectangle) => { height: 870, ...windowBounds, webPreferences: { + webSecurity: false, nodeIntegration: isTest, webviewTag: false, // @ts-ignore ts-migrate(2322) FIXME: Type '{ nodeIntegration: boolean; webviewTag: fals... Remove this comment to see the full error message @@ -62,6 +64,30 @@ export const createMainWindow = (locale: string, windowBounds?: Rectangle) => { // Construct new BrowserWindow const window = new BrowserWindow(windowOptions); + + window.webContents.session.webRequest.onBeforeSendHeaders( + (details, callback) => { + callback({ requestHeaders: { Origin: '*', ...details.requestHeaders } }); + } + ); + + window.webContents.session.webRequest.onHeadersReceived( + (details, callback) => { + callback({ + responseHeaders: { + 'Content-Security-Policy': + "connect-src 'https://mainnet.infura.io' 'https://polygon-mainnet.infura.io' 'unsafe-eval';", + 'Access-Control-Allow-Origin': [ + 'https://mainnet.infura.io', + 'https://polygon-mainnet.infura.io', + '*', + ], + ...details.responseHeaders, + }, + }); + } + ); + rendererErrorHandler.setup(window, createMainWindow); const { minWindowsWidth, minWindowsHeight } = getContentMinimumSize(window); window.setMinimumSize(minWindowsWidth, minWindowsHeight); diff --git a/source/renderer/app/components/wallet/WalletSendForm.tsx b/source/renderer/app/components/wallet/WalletSendForm.tsx index fb6b723c79..d4a36d518d 100755 --- a/source/renderer/app/components/wallet/WalletSendForm.tsx +++ b/source/renderer/app/components/wallet/WalletSendForm.tsx @@ -65,6 +65,7 @@ type Props = { selectedAsset: Asset | null | undefined; isLoadingAssets: boolean; isDialogOpen: (...args: Array) => any; + isDomainAddress: (address: string) => boolean; isRestoreActive: boolean; isHardwareWallet: boolean; hwDeviceStatus: HwDeviceStatus; @@ -72,6 +73,7 @@ type Props = { onUnsetActiveAsset: (...args: Array) => any; onExternalLinkClick: (...args: Array) => any; isAddressFromSameWallet: boolean; + resolveDomain: (value: string) => string; tokenFavorites: Record; walletName: string; onTokenPickerDialogOpen: (...args: Array) => any; @@ -335,7 +337,7 @@ class WalletSendForm extends Component { value: '', validators: [ async ({ field, form }) => { - const { value } = field; + let { value } = field; if (value === null || value === '') { this.resetTransactionFee(); @@ -346,6 +348,14 @@ class WalletSendForm extends Component { ]; } + if (value.split('.').length > 1) { + try { + value = await this.props.resolveDomain(value); + } catch (error) { + return [false, error.message]; + } + } + const isValid = await this.props.addressValidator(value); if (isValid && this.isAddressFromSameWallet()) { diff --git a/source/renderer/app/components/wallet/send-form/messages.ts b/source/renderer/app/components/wallet/send-form/messages.ts index b927aa0317..39947c84cd 100644 --- a/source/renderer/app/components/wallet/send-form/messages.ts +++ b/source/renderer/app/components/wallet/send-form/messages.ts @@ -33,6 +33,26 @@ export default defineMessages({ defaultMessage: '!!!Ada', description: 'Label for the "Ada" input in the wallet send form.', }, + domainUnsupported: { + id: 'wallet.send.form.unsupported', + defaultMessage: '!!!Unsupported Domain', + description: 'Unsupported Domain Error', + }, + domainNotFound: { + id: 'wallet.send.form.recordNotFound', + defaultMessage: '!!!No Cardano record found for this domain', + description: 'Not Found domain error', + }, + domainUnregistered: { + id: 'wallet.send.form.unregistered', + defaultMessage: '!!!Domaon not registered', + description: 'Domain not registered error', + }, + networkLabel: { + id: 'wallet.receive.pdf.networkLabel', + defaultMessage: '!!!Cardano Network:', + description: 'PDF networkLabel', + }, removeLabel: { id: 'wallet.send.form.button.removeLabel', defaultMessage: '!!!Remove', diff --git a/source/renderer/app/containers/wallet/WalletSendPage.tsx b/source/renderer/app/containers/wallet/WalletSendPage.tsx index b129b7ebc2..dee6d60f36 100755 --- a/source/renderer/app/containers/wallet/WalletSendPage.tsx +++ b/source/renderer/app/containers/wallet/WalletSendPage.tsx @@ -108,7 +108,12 @@ class WalletSendPage extends Component { hardwareWallets, assets: assetsStore, } = stores; - const { isValidAddress, isAddressFromSameWallet } = wallets; + const { + isValidAddress, + isAddressFromSameWallet, + isDomainAddress, + resolveDomain, + } = wallets; const { validateAmount, validateAssetAmount } = transactions; const { hwDeviceStatus } = hardwareWallets; const hasAssetsEnabled = WALLET_ASSETS_ENABLED; @@ -154,6 +159,7 @@ class WalletSendPage extends Component { selectedAsset={selectedAsset} isLoadingAssets={isLoadingAssets} isDialogOpen={uiDialogs.isOpen} + isDomainAddress={isDomainAddress} isRestoreActive={wallet.isRestoring} isHardwareWallet={isHardwareWallet} hwDeviceStatus={hwDeviceStatus} @@ -161,6 +167,7 @@ class WalletSendPage extends Component { onUnsetActiveAsset={unsetActiveAsset.trigger} onExternalLinkClick={app.openExternalLink} isAddressFromSameWallet={isAddressFromSameWallet} + resolveDomain={resolveDomain} tokenFavorites={favorites} walletName={walletName} onTokenPickerDialogOpen={this.openTokenPickerDialog} diff --git a/source/renderer/app/i18n/locales/en-US.json b/source/renderer/app/i18n/locales/en-US.json index e62d0e2320..e78a9aee06 100755 --- a/source/renderer/app/i18n/locales/en-US.json +++ b/source/renderer/app/i18n/locales/en-US.json @@ -1243,6 +1243,10 @@ "wallet.transaction.receiverLabel": "Receiver", "wallet.transaction.rewards.from": "From rewards", "wallet.transaction.sent": "{transactionsType} sent", + "wallet.transaction.send.domain.invalid": "Invalid Domain", + "wallet.transaction.send.domain.recordNotFound": "No Cardano record found for this domain", + "wallet.transaction.send.domain.unregistered": "Domain is not registered", + "wallet.transaction.send.domain.unsupported": "Domain is not supported", "wallet.transaction.state.confirmed": "Transaction confirmed", "wallet.transaction.state.confirmedHeading": "Confirmed", "wallet.transaction.state.failed": "Transaction failed", diff --git a/source/renderer/app/stores/WalletsStore.ts b/source/renderer/app/stores/WalletsStore.ts index f7f4799e0a..20ad3db3b5 100644 --- a/source/renderer/app/stores/WalletsStore.ts +++ b/source/renderer/app/stores/WalletsStore.ts @@ -17,6 +17,7 @@ import { logger } from '../utils/logging'; import { ROUTES } from '../routes-config'; import { formattedWalletAmount } from '../utils/formatters'; import { ellipsis } from '../utils/strings'; +import { resolveUNSAddress } from '../../../common/utils/unsResolution'; import { bech32EncodePublicKey, isReceiverAddressType, @@ -1138,10 +1139,21 @@ export default class WalletsStore extends Store { } }); }; + + isDomainAddress = (address: string): Boolean => { + return address.split('.').length > 0; + }; + isValidAddress = async (address: string) => { const { network } = this.environment; const expectedNetworkTag = get(NetworkMagics, [network]); - const validAddressStyles: AddressStyle[] = ['Byron', 'Icarus', 'Shelley']; + const validAddressStyles: AddressStyle[] = [ + 'Byron', + 'Icarus', + 'Shelley', + 'ENS', + 'UNS', + ]; this.isAddressFromSameWallet = false; if (!expectedNetworkTag) { @@ -1241,6 +1253,14 @@ export default class WalletsStore extends Store { this.stores.transactions.transactionsRequests = []; this.isAddressFromSameWallet = false; }; + + @action + resolveDomain = async (address: string) => { + const { currentLocale } = this.stores.profile; + const intl = i18nContext(currentLocale); + return (address = await resolveUNSAddress(address, intl)); + }; + @action _importWalletFromFile = async (params: WalletImportFromFileParams) => { const { filePath, walletName, spendingPassword } = params; diff --git a/yarn.lock b/yarn.lock index 499acabd4e..7621388518 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2012,6 +2012,164 @@ minimatch "^3.0.4" strip-json-comments "^3.1.1" +"@ethersproject/abi@^5.0.1": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abi/-/abi-5.6.0.tgz#ea07cbc1eec2374d32485679c12408005895e9f3" + dependencies: + "@ethersproject/address" "^5.6.0" + "@ethersproject/bignumber" "^5.6.0" + "@ethersproject/bytes" "^5.6.0" + "@ethersproject/constants" "^5.6.0" + "@ethersproject/hash" "^5.6.0" + "@ethersproject/keccak256" "^5.6.0" + "@ethersproject/logger" "^5.6.0" + "@ethersproject/properties" "^5.6.0" + "@ethersproject/strings" "^5.6.0" + +"@ethersproject/abstract-provider@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-provider/-/abstract-provider-5.6.0.tgz#0c4ac7054650dbd9c476cf5907f588bbb6ef3061" + dependencies: + "@ethersproject/bignumber" "^5.6.0" + "@ethersproject/bytes" "^5.6.0" + "@ethersproject/logger" "^5.6.0" + "@ethersproject/networks" "^5.6.0" + "@ethersproject/properties" "^5.6.0" + "@ethersproject/transactions" "^5.6.0" + "@ethersproject/web" "^5.6.0" + +"@ethersproject/abstract-signer@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@ethersproject/abstract-signer/-/abstract-signer-5.6.0.tgz#9cd7ae9211c2b123a3b29bf47aab17d4d016e3e7" + dependencies: + "@ethersproject/abstract-provider" "^5.6.0" + "@ethersproject/bignumber" "^5.6.0" + "@ethersproject/bytes" "^5.6.0" + "@ethersproject/logger" "^5.6.0" + "@ethersproject/properties" "^5.6.0" + +"@ethersproject/address@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@ethersproject/address/-/address-5.6.0.tgz#13c49836d73e7885fc148ad633afad729da25012" + dependencies: + "@ethersproject/bignumber" "^5.6.0" + "@ethersproject/bytes" "^5.6.0" + "@ethersproject/keccak256" "^5.6.0" + "@ethersproject/logger" "^5.6.0" + "@ethersproject/rlp" "^5.6.0" + +"@ethersproject/base64@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@ethersproject/base64/-/base64-5.6.0.tgz#a12c4da2a6fb86d88563216b0282308fc15907c9" + dependencies: + "@ethersproject/bytes" "^5.6.0" + +"@ethersproject/bignumber@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@ethersproject/bignumber/-/bignumber-5.6.0.tgz#116c81b075c57fa765a8f3822648cf718a8a0e26" + dependencies: + "@ethersproject/bytes" "^5.6.0" + "@ethersproject/logger" "^5.6.0" + bn.js "^4.11.9" + +"@ethersproject/bytes@^5.6.0": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@ethersproject/bytes/-/bytes-5.6.1.tgz#24f916e411f82a8a60412344bf4a813b917eefe7" + dependencies: + "@ethersproject/logger" "^5.6.0" + +"@ethersproject/constants@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@ethersproject/constants/-/constants-5.6.0.tgz#55e3eb0918584d3acc0688e9958b0cedef297088" + dependencies: + "@ethersproject/bignumber" "^5.6.0" + +"@ethersproject/hash@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@ethersproject/hash/-/hash-5.6.0.tgz#d24446a5263e02492f9808baa99b6e2b4c3429a2" + dependencies: + "@ethersproject/abstract-signer" "^5.6.0" + "@ethersproject/address" "^5.6.0" + "@ethersproject/bignumber" "^5.6.0" + "@ethersproject/bytes" "^5.6.0" + "@ethersproject/keccak256" "^5.6.0" + "@ethersproject/logger" "^5.6.0" + "@ethersproject/properties" "^5.6.0" + "@ethersproject/strings" "^5.6.0" + +"@ethersproject/keccak256@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@ethersproject/keccak256/-/keccak256-5.6.0.tgz#fea4bb47dbf8f131c2e1774a1cecbfeb9d606459" + dependencies: + "@ethersproject/bytes" "^5.6.0" + js-sha3 "0.8.0" + +"@ethersproject/logger@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@ethersproject/logger/-/logger-5.6.0.tgz#d7db1bfcc22fd2e4ab574cba0bb6ad779a9a3e7a" + +"@ethersproject/networks@^5.6.0": + version "5.6.1" + resolved "https://registry.yarnpkg.com/@ethersproject/networks/-/networks-5.6.1.tgz#7a21ed1f83e86121737b16841961ec99ccf5c9c7" + dependencies: + "@ethersproject/logger" "^5.6.0" + +"@ethersproject/properties@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@ethersproject/properties/-/properties-5.6.0.tgz#38904651713bc6bdd5bdd1b0a4287ecda920fa04" + dependencies: + "@ethersproject/logger" "^5.6.0" + +"@ethersproject/rlp@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@ethersproject/rlp/-/rlp-5.6.0.tgz#55a7be01c6f5e64d6e6e7edb6061aa120962a717" + dependencies: + "@ethersproject/bytes" "^5.6.0" + "@ethersproject/logger" "^5.6.0" + +"@ethersproject/signing-key@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@ethersproject/signing-key/-/signing-key-5.6.0.tgz#4f02e3fb09e22b71e2e1d6dc4bcb5dafa69ce042" + dependencies: + "@ethersproject/bytes" "^5.6.0" + "@ethersproject/logger" "^5.6.0" + "@ethersproject/properties" "^5.6.0" + bn.js "^4.11.9" + elliptic "6.5.4" + hash.js "1.1.7" + +"@ethersproject/strings@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@ethersproject/strings/-/strings-5.6.0.tgz#9891b26709153d996bf1303d39a7f4bc047878fd" + dependencies: + "@ethersproject/bytes" "^5.6.0" + "@ethersproject/constants" "^5.6.0" + "@ethersproject/logger" "^5.6.0" + +"@ethersproject/transactions@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@ethersproject/transactions/-/transactions-5.6.0.tgz#4b594d73a868ef6e1529a2f8f94a785e6791ae4e" + dependencies: + "@ethersproject/address" "^5.6.0" + "@ethersproject/bignumber" "^5.6.0" + "@ethersproject/bytes" "^5.6.0" + "@ethersproject/constants" "^5.6.0" + "@ethersproject/keccak256" "^5.6.0" + "@ethersproject/logger" "^5.6.0" + "@ethersproject/properties" "^5.6.0" + "@ethersproject/rlp" "^5.6.0" + "@ethersproject/signing-key" "^5.6.0" + +"@ethersproject/web@^5.6.0": + version "5.6.0" + resolved "https://registry.yarnpkg.com/@ethersproject/web/-/web-5.6.0.tgz#4bf8b3cbc17055027e1a5dd3c357e37474eaaeb8" + dependencies: + "@ethersproject/base64" "^5.6.0" + "@ethersproject/bytes" "^5.6.0" + "@ethersproject/logger" "^5.6.0" + "@ethersproject/properties" "^5.6.0" + "@ethersproject/strings" "^5.6.0" + "@faker-js/faker@6.0.0": version "6.0.0" resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-6.0.0.tgz#b613ebf5f5ebb2ab987afb567d8b7fe860199c13" @@ -3512,6 +3670,17 @@ "@typescript-eslint/types" "5.10.1" eslint-visitor-keys "^3.0.0" +"@unstoppabledomains/resolution@^7.1.4": + version "7.1.4" + resolved "https://registry.yarnpkg.com/@unstoppabledomains/resolution/-/resolution-7.1.4.tgz#f3f0f9d2f36b88bf0a3af92c26731a01e5dba94b" + dependencies: + "@ethersproject/abi" "^5.0.1" + bn.js "^4.4.0" + cross-fetch "^3.1.4" + elliptic "^6.5.4" + js-sha256 "^0.9.0" + js-sha3 "^0.8.0" + "@wdio/config@5.18.4": version "5.18.4" resolved "https://registry.yarnpkg.com/@wdio/config/-/config-5.18.4.tgz#cabbac2f42bb1f8ac768f79d0e7671976d97d30e" @@ -4951,7 +5120,7 @@ bn.js@^4.0.0, bn.js@^4.1.0, bn.js@^4.11.8: version "4.11.9" resolved "https://registry.yarnpkg.com/bn.js/-/bn.js-4.11.9.tgz#26d556829458f9d1e81fc48952493d0ba3507828" -bn.js@^4.11.9: +bn.js@^4.11.9, bn.js@^4.4.0: version "4.12.0" resolved "https://registry.npmjs.org/bn.js/-/bn.js-4.12.0.tgz#775b3f278efbb9718eec7361f483fb36fbbfea88" @@ -7372,7 +7541,7 @@ element-resize-detector@^1.2.1: dependencies: batch-processor "1.0.0" -elliptic@6.5.4, elliptic@^6.4.0, elliptic@^6.5.2, elliptic@^6.5.3: +elliptic@6.5.4, elliptic@^6.4.0, elliptic@^6.5.2, elliptic@^6.5.3, elliptic@^6.5.4: version "6.5.4" resolved "https://registry.npmjs.org/elliptic/-/elliptic-6.5.4.tgz#da37cebd31e79a1367e941b592ed1fbebd58abbb" dependencies: @@ -10916,6 +11085,18 @@ js-chain-libs-node@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/js-chain-libs-node/-/js-chain-libs-node-0.3.0.tgz#bb23f6ba3c724ced923a1bb0fe82f16b06fcf72b" +js-sha256@^0.9.0: + version "0.9.0" + resolved "https://registry.yarnpkg.com/js-sha256/-/js-sha256-0.9.0.tgz#0b89ac166583e91ef9123644bd3c5334ce9d0966" + +js-sha3@0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + +js-sha3@^0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/js-sha3/-/js-sha3-0.8.0.tgz#b9b7a5da73afad7dedd0f8c463954cbde6818840" + "js-tokens@^3.0.0 || ^4.0.0", js-tokens@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"